diff --git a/.editorconfig b/.editorconfig index 096ff2565..2e54d0f2d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,9 @@ trim_trailing_whitespace = true insert_final_newline = true ; Not change VS generated files -[*.{sln,csroj}] +[*.{sln,csproj}] trim_trailing_whitespace = false insert_final_newline = false + +[*.{props,targets,csproj,config}] +indent_size = 2 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index be48d6222..9d132e955 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,5 +3,3 @@ # Custom for Visual Studio *.cs diff=csharp -*.sln merge=union -*.csproj merge=union diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 000000000..c37520864 --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,17 @@ +You are opening a _bug report_ against the LibGit2Sharp project: we +use GitHub Issues for tracking bug reports and feature requests. If +you have a question about an API or usage, please ask on StackOverflow: +http://stackoverflow.com/questions/tagged/libgit2sharp. + +Otherwise, to report a bug, please fill out the reproduction steps +(below) and delete these introductory paragraphs. Thanks! + +### Reproduction steps + +### Expected behavior + +### Actual behavior + +### Version of LibGit2Sharp (release number or SHA1) + +### Operating system(s) tested; .NET runtime tested diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..54837ac35 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,102 @@ +name: CI +on: + push: + branches: [master, release-*] + tags: + - '[0-9]+.[0-9]+.[0-9]+' + - '[0-9]+.[0-9]+.[0-9]+-*' + pull_request: + workflow_dispatch: +env: + DOTNET_NOLOGO: true +jobs: + build: + name: Build + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4.1.2 + with: + fetch-depth: 0 + - name: Install .NET SDK + uses: actions/setup-dotnet@v4.0.0 + with: + dotnet-version: 9.0.x + - name: Build + run: dotnet build LibGit2Sharp.sln --configuration Release + - name: Upload packages + uses: actions/upload-artifact@v4.3.1 + with: + name: NuGet packages + path: artifacts/package/ + retention-days: 7 + - name: Verify trimming compatibility + run: dotnet publish TrimmingTestApp + test: + name: Test / ${{ matrix.os }} / ${{ matrix.arch }} / ${{ matrix.tfm }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + arch: [ x64 ] + os: [ windows-2019, windows-2022, macos-13 ] + tfm: [ net472, net8.0, net9.0 ] + exclude: + - os: macos-13 + tfm: net472 + include: + - arch: arm64 + os: macos-14 + tfm: net8.0 + - arch: arm64 + os: macos-14 + tfm: net9.0 + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4.1.2 + with: + fetch-depth: 0 + - name: Install .NET SDK + uses: actions/setup-dotnet@v4.0.0 + with: + dotnet-version: | + 9.0.x + 8.0.x + - name: Run ${{ matrix.tfm }} tests + run: dotnet test LibGit2Sharp.sln --configuration Release --framework ${{ matrix.tfm }} --logger "GitHubActions" /p:ExtraDefine=LEAKS_IDENTIFYING + test-linux: + name: Test / ${{ matrix.distro }} / ${{ matrix.arch }} / ${{ matrix.tfm }} + runs-on: ${{ matrix.runnerImage }} + strategy: + matrix: + arch: [ amd64, arm64 ] + distro: [ alpine.3.17, alpine.3.18, alpine.3.19, alpine.3.20, centos.stream.9, debian.12, fedora.40, ubuntu.20.04, ubuntu.22.04, ubuntu.24.04 ] + sdk: [ '8.0', '9.0' ] + exclude: + - distro: alpine.3.17 + sdk: '9.0' + - distro: alpine.3.18 + sdk: '9.0' + - distro: alpine.3.19 + sdk: '9.0' + include: + - sdk: '8.0' + tfm: net8.0 + - sdk: '9.0' + tfm: net9.0 + - arch: amd64 + runnerImage: ubuntu-22.04 + - arch: arm64 + runnerImage: ubuntu-22.04-arm + fail-fast: false + steps: + - name: Checkout + uses: actions/checkout@v4.1.2 + with: + fetch-depth: 0 + - name: Run ${{ matrix.tfm }} tests + run: | + git_command="git config --global --add safe.directory /app" + test_command="dotnet test LibGit2Sharp.sln --configuration Release -p:TargetFrameworks=${{ matrix.tfm }} --logger "GitHubActions" -p:ExtraDefine=LEAKS_IDENTIFYING" + docker run -t --rm --platform linux/${{ matrix.arch }} -v "$PWD:/app" -e OPENSSL_ENABLE_SHA1_SIGNATURES=1 gittools/build-images:${{ matrix.distro }}-sdk-${{ matrix.sdk }} sh -c "$git_command && $test_command" + diff --git a/.gitignore b/.gitignore index 842317924..32e17b4d0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Thumbs.db *_p.c *.ncb *.suo +.vs/ *.sln.ide/ *.tlb *.tlh @@ -35,9 +36,7 @@ _ReSharper*/ *.userprefs *.swp *.DotSettings -#Ignore custom generated files -LibGit2Sharp/Core/UniqueIdentifier.cs -!Lib/NativeBinaries/*/*.pdb -!nuget.package/build/ _NCrunch_LibGit2Sharp/ +artifacts/ +worktree.playlist diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 30eb68a28..000000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "libgit2"] - path = libgit2 - url = https://github.com/libgit2/libgit2.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6b9c47e64..000000000 --- a/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -# Travis-CI Build for libgit2sharp -# see travis-ci.org for details - -language: c - -os: - - osx - - linux - -before_install: - - date -u - - uname -a - - export PATH=/opt/mono/bin:$PATH - - env | sort - -# Make sure CMake and Mono are installed -install: - - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/travis.linux.install.deps.sh; fi - - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/travis.osx.install.deps.sh; fi - -# Build libgit2, LibGit2Sharp and run the tests -script: - - ./build.libgit2sharp.sh - -# Only watch the development branch -branches: - only: - - vNext - -# Notify of build changes -notifications: - email: - - emeric.fermas@gmail.com diff --git a/CHANGES.md b/CHANGES.md index 9dee434dc..a00b598d7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,14 +1,281 @@ # LibGit2Sharp Changes -**LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono.** - - - Source code: - - NuGet package: - - Issue tracker: - - @libgit2sharp: - - CI servers: - - CodeBetter TeamCity: - - Travis: +## v0.31 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.30.0..0.31.0)) + +### Changes +- This release includes [libgit2 v1.8.4](https://github.com/libgit2/libgit2/releases/tag/v1.8.4). + - SSH is now supported through [libgit2's support for OpenSSH](https://github.com/libgit2/libgit2/pull/6617). +- The ppc64le architecture is now supported on Linux. +- .NET 6 has reached end of support, so LibGit2Sharp now targets `net472` and `net8.0`. + +### Additions +- Adds Depth to FetchOptions allowing for shallow cloning [#2070](https://github.com/libgit2/libgit2sharp/pull/2070) +- Make owner validation configurable [#2093](https://github.com/libgit2/libgit2sharp/pull/2093) +- Add a CloneOptions constructor that takes a FetchOptions [#2132](https://github.com/libgit2/libgit2sharp/pull/2132) + +### Fixes +- TreeDefinition.Remove fails to remove unwrapped trees [#1869](https://github.com/libgit2/libgit2sharp/issues/1869) +- ObjectDatabase.Write(Stream stream...) overload does not respect T [#2071](https://github.com/libgit2/libgit2sharp/issues/2071) +- Repository.Worktrees.Add leaves now worktree empty [#2037](https://github.com/libgit2/libgit2sharp/issues/2037) + +## v0.30 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.29.0..0.30.0)) + +### Changes +- This release includes [libgit2 v1.7.2](https://github.com/libgit2/libgit2/releases/tag/v1.7.2). +- Updates for trimming compatibility [#2084](https://github.com/libgit2/libgit2sharp/pull/2084) +- Updates for .NET 8 [#2085](https://github.com/libgit2/libgit2sharp/pull/2085) + +## v0.29 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.28.0..0.29.0)) + +### Changes +- This release includes [libgit2 v1.7.1](https://github.com/libgit2/libgit2/releases/tag/v1.7.1). + - CI changes for the native binaries has removed support for CentOS 7. See [#2066](https://github.com/libgit2/libgit2sharp/pull/2066) for details. + +### Additions +- Add proxy options [#2065](https://github.com/libgit2/libgit2sharp/pull/2065) + - See PR for details, including some breaking changes to `CloneOptions` and `SubmoduleUpdateOptions` + +## v0.28 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.27.2..0.28.0)) + +### Additions +- Add CustomHeaders to PushOptions [#2052](https://github.com/libgit2/libgit2sharp/pull/2052) + +## v0.27.2 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.27.1..0.27.2)) + +### Changes +- This release includes [libgit2 v1.6.4](https://github.com/libgit2/libgit2/releases/tag/v1.6.4). + +### Fixes +- Can't access GIT config (Repository.Config) since v0.27.0 [#2031](https://github.com/libgit2/libgit2sharp/issues/2031) + +## v0.27.1 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.27.0..0.27.1)) + +### Fixes +- AssemblyVersion of v0.27.0 is `0.0.0.0`, which is lower than the AssemblyVersion of the v0.26.x releases. [#2030](https://github.com/libgit2/libgit2sharp/pull/2030) + +## v0.27 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.26..0.27.0)) + +### Changes +- LibGit2Sharp now targets .NET Framework 4.7.2 and .NET 6. +- This release includes [libgit2 v1.6.3](https://github.com/libgit2/libgit2/releases/tag/v1.6.3). +- Changes to the native binaries let LibGit2Sharp work on all [.NET 6 supported OS versions and architectures](https://github.com/dotnet/core/blob/main/release-notes/6.0/supported-os.md). +- `GlobalSetings.NativeLibraryPath` used to automatically append architecture to the path when running on .NET Framework. This behavior has been removed to make it consistent. [#1918](https://github.com/libgit2/libgit2sharp/pull/1918) + +### Additions +- Add support for adding and clearing multi-valued configuration [#1720](https://github.com/libgit2/libgit2sharp/pull/1720) +- added lines and deleted lines in content changes [#1790](https://github.com/libgit2/libgit2sharp/pull/1790) +- Set / get supported extensions [#1908](https://github.com/libgit2/libgit2sharp/pull/1908) +- Simplify dealing with missing git objects [#1909](https://github.com/libgit2/libgit2sharp/pull/1909) +- Throw NotFoundException if trees are missing when computing diff [#1936](https://github.com/libgit2/libgit2sharp/pull/1936) + +### Fixes +- Adjust GitStatusOptions to match structure of native libgit2 [#1884](https://github.com/libgit2/libgit2sharp/pull/1884) +- Update git_worktree_add_options struct to include ref pointer [#1890](https://github.com/libgit2/libgit2sharp/pull/1890) +- Fix git_remote_connect not throwing on non-zero result [#1913](https://github.com/libgit2/libgit2sharp/pull/1913) +- Fix incorrect information in exceptions [#1919](https://github.com/libgit2/libgit2sharp/pull/1919) +- Checkout branch looks to remote tracking branches as fallback [#1820](https://github.com/libgit2/libgit2sharp/pull/1820) +- Fixed calling into native libgit2 on osx-arm64 [#1955](https://github.com/libgit2/libgit2sharp/pull/1955) + +## v0.26 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.25..v0.26)) + +### Additions + +* Add `CherryPickCommitIntoIndex` to `ObjectDatabase` +* The underlying native library (libgit2) now no longer relies on libcurl +* The underlying native library now no longer relies on zlib +* Add `IndentHeuristic` option to `CompareOptions` + +## v0.25 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.24..v0.25)) + +LibGit2Sharp is now .NET Core 2.0+ and .NET Framework compatible. + +### Additions + + - `GitObject` now has a `Peel` method that will let you peel (for example) + a `Tag` to a `Tree`. + - `MergeOptions` now includes an option to `IgnoreWhitespaceChanges`. + - `TreeDefinition` can now `Add` an object with only the ID, which allows + users of large files to add entries without realizing a `Blob`. + - `ObjectDatabase` can now `Write` a `Stream`, which allows users of + large files to stream an object into storage without loading it into + memory. + - `ObjectDatabase` can now `MergeCommitsIntoIndex` allowing users to perform + an in-memory merge that produces an `Index` structure with conflicts. + - Users can enable or disable dependent object existence checks when + creating new objects with `GlobalSettings.SetEnableStrictObjectCreation` + - Users can enable or disable `ofs_delta` support with + `GlobalSettings.SetEnableOfsDelta` + +### Changes + + - Status now does not show untracked files by default. To retrieve + untracked files, included the `StatusOptions.IncludeUntracked` and/or + the `StatusOptions.RecurseUntrackedDirs` options. + - Status now does not show the ignored files by default. To retrieve + ignored files, include the `StatusOptions.IncludeIgnored` option. + - `Commands.Pull` can now provide a `null` value for `PullOptions`, + which indicates that default values should be used. + +### Fixes + + - The exception thrown when the native library cannot be loaded is now + able to be caught and will no longer crash the process. + - Getting the `Notes` collection from a `Repository` no longer throws an + exception when the repository has no notes. + +## v0.24 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.23..v0.24)) + +This is the last release before a moving to .NET Core compatible library. + +It will be the last supported release with the prior architecture; as a +result, this release is primarily bugfixes and does not include major new +APIs. + +## v0.23 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.22..v0.23)) + +### Additions + + - Add `CherryPickCommit` and `RevertCommit` to `ObjectDatabase`. + - Add `IncludeIgnored` field to `SatusOptions`. + - Add `Commit.CreateBuffer` to write a commit object to a buffer and + `ObjectDatabase.CreateCommitWithSignature` to create commits which include a + signature. + - Add `Commit.ExtractSignature` to get a commit's signature. + - Add `ObjectDatabase.Write` to write arbitrary objects to the object db. + - Add `Commit.PrettifyMessage` + + +### Changes + + - The native libraries are now expected to be in the `lib` directory, + instead of `NativeBinaries` for improved mono compatibility. In + addition, the names of platform architectures now better reflect + the vendor naming (eg, `x86_64` instead of `amd64` on Linux). + - Deprecate the config paths in RepositoryOptions + - Deprecate the `QueryBy` overload with `FollowFilter`. + - Deprecate `Branch.Remote` in favour of `Branch.RemoteName` + - `Remote` no longer implement the equality operator. + - `Remote.Update` takes a remote name instead of an instance. + - `Fetch`, `Pull`, `Move`, `Remove`, `Stage` are now in a commands namespace to + indicate what they represent. + +## v0.22 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.21.1...v0.22)) + +### Additions + + - Add CustomHeaders in the push options (#1217) + - Expose the minimal diff algorithm (#1229) + - Expose Reset() with checkout options (#1219) + - Add a prettify option to history rewrite options (#1185) + - Add option to describe to only follow the first parent (#1190) + - Allow setting the config search path (#1123) + - Provide access to the remote's host HTTPS certificate (#1134) + - Add support for rebase (#964) + - ListReferences() now accepts a credentials provider (#1099) + - Introduce FileStatus.Conflicted and introduce staging of conflicts (#1062) + - Support streaming filters written in C# (#1030) + - Add support for the pre-push callback (#1061) + - Add support for listing remote references without a Repository instance (#1065) + - Add StashCollection.Apply() and .Pop() (#1068) + - Support retrieving a configuration for a repository without instantiating it (#1042) + - Implement 'log --follow'-like functionality (#963) + - Introduce in-memory merging via Repository.MergeCommits() (#990) + - Allow setting whether to prune during a fetch (#1258) + +### Changes + + - Deprecate MergeConflictException in a backwards-compatible way (#1243) + - Improve type safety in the generic type for Diff.Compare() (#1180) + - Obsolete Repository.Commit(), NoteCollection.Add() and + NoteCollection.Remove() overloads which do not require a signature (#1173) + - BuildSignature() no longer tries to build a signature from the + environment if there is none configured (#1171) + - Rename the commit walker's Since to IncludeReachableFrom and Until to ExcludeReachableFrom (#1069) + - Rename MergeConflictException to CheckoutConflictException to more + accurately reflect what it means (#1059) + - Specify the diff algorithm instead of setting a boolean to use patience (#1043) + - Remove optional parameters (#1031) + - Move Repository.Reset(paths) into Index (#959) + - Move FindMergeBase() overloads to ObjectDatabase (#957) + +### Fixes + + - ListReferences() is now able to handle symbolic references (#1132) + - Repository.IsValid() returns false on empty paths (#1156) + - The included version of libgit2 includes racy-git support + - Fix a racy NRE in the filters (#1113) + +## v0.21.1 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.21...v0.21.1)) + +### Changes + +- Fix Fetch() related tests to cope with recent GitHub policy change regarding include-tag handling (#995, #1001) + +## v0.21 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.20.2...v0.21)) + +### Additions + + - Introduce repo.Index.Add() and repo.Index.Remove() (#907) + - Introduce repo.Describe() (#848) + - Teach Repository.Clone to accept a specific branch to checkout (#650, #882) + - Expose IndexEntry.AssumeUnchanged (#928, #929) + - Introduce GlobalSettings.Version.InformationalVersion (#921) + +### Changes + + - Deprecate Branch.Checkout() (#937) + - Deprecate GlobalSettings.Version.MajorMinorPatch (#921) + - Change Blob.Size output to a long (#892) + - Update libgit2 binaries to libgit2/libgit2@e0902fb + +### Fixes + + - Fix Network.Fetch() tags retrieval (#927) + - Fix repo.Stage("*") behavior (#907) + - Plug some memory leaks (#883, #910) + - Protect Repository.Clone() from null parameters (#891) + +## v0.20.2 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.20.1...v0.20.2)) + +### Fixes + + - Update libgit2 to prevent issues around symbolic links to ".git" folders in trees on Mac + +## v0.20.1 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.20...v0.20.1)) + +### Fixes + + - Update libgit2 to prevent issues around ".git" folders in trees on Windows and Mac + +## v0.20 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.19...v0.20)) + +### Additions + + - Teach RemoteUpdater to update the remote url (#803) + - Introduce ObjectDatabase.CreateTree(Index) and Index.Reset(Tree) (#788, #799) + - Add process wide logging feature (#832) + - Add process wide SmartSubtransport registration/unregistration (#528) + - Expose Index.Clear() (#814) + +### Changes + + - Require Mono 3.6+ on non Windows platform (#800) + - Require NuGet 2.7+ to install the package (#836) + - Throw MergeFetchHeadNotFoundException when Pull cannot find ref to merge (#841) + - Drop Remote.IsSupportedUrl() (#857) + - Deprecate repo.Version in favor of GlobalSettings.Version (#726, #820) + - Remove optional parameters from IRepository (#779, #815) + - Move higher level Index operations to IRepository (#822, #851) + - Deprecate repo.Refs.Move() in favor of repo.Refs.Rename() (#752, #819) + - Update libgit2 binaries to libgit2/libgit2@3f8d005 + +### Fixes + + - Fix compareOptions handling in Diff.Compare (#827, #828) + - Honor MSBuild Publish mechanism (#597, #821) + - Make Configuration.BuildSignature() throw a more descriptive message (#831, #858) + - Prevent Branch.Remote property from throwing when the remote is unresolvable (#823) + - Teach Revert() to clean up repository state when there is nothing to revert (#816) ## v0.19 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.18.1...v0.19)) diff --git a/CI/build.msbuild b/CI/build.msbuild deleted file mode 100644 index 5a94395e7..000000000 --- a/CI/build.msbuild +++ /dev/null @@ -1,55 +0,0 @@ - - - Release - $(MSBuildProjectDirectory)\.. - $(RootDir)\LibGit2Sharp.Tests\bin\$(Configuration) - $(RootDir)\Build - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/CI/travis.linux.install.deps.sh b/CI/travis.linux.install.deps.sh deleted file mode 100755 index 1a820d07b..000000000 --- a/CI/travis.linux.install.deps.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -ev - -sudo sh -c "echo 'deb http://download.opensuse.org/repositories/home:/tpokorra:/mono/xUbuntu_12.04/ /' >> /etc/apt/sources.list.d/mono-opt.list" - -curl http://download.opensuse.org/repositories/home:/tpokorra:/mono/xUbuntu_12.04/Release.key | sudo apt-key add - - -sudo apt-get update -sudo apt-get install mono-opt cmake diff --git a/CI/travis.osx.install.deps.sh b/CI/travis.osx.install.deps.sh deleted file mode 100755 index c6621b735..000000000 --- a/CI/travis.osx.install.deps.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -ev - -MONO_VER=3.6.0 - -brew update -which cmake || brew install cmake - -wget "http://download.mono-project.com/archive/${MONO_VER}/macos-10-x86/MonoFramework-MDK-${MONO_VER}.macos10.xamarin.x86.pkg" -sudo installer -pkg "MonoFramework-MDK-${MONO_VER}.macos10.xamarin.x86.pkg" -target / diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..218cb2a28 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,58 @@ +# How to Contribute + +We love Pull Requests! Your contributions help make LibGit2Sharp great. + +## Getting Started + +So you want to contribute to LibGit2Sharp. Great! Contributions take many forms from +submitting issues, writing documentation, to making code changes. We welcome it all. + +But first things first... + +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version that you know has the issue. +* Fork the repository on GitHub, then clone it using your favorite Git client. +* Make sure the project builds and all tests pass on your machine by running + the `buildandtest.cmd` script on Windows or `buildandtest.sh` on Linux/Mac. + +## LibGit2 + +LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono. +LibGit2 is a git submodule referencing the [libgit2 project](https://github.com/libgit2/libgit2). To learn more about +submodules read [here](http://git-scm.com/book/en/v2/Git-Tools-Submodules). +To build libgit2 see [here](https://github.com/libgit2/libgit2sharp/wiki/How-to-build-x64-libgit2-and-LibGit2Sharp). + +## Making Changes + +Make sure you have the required .NET Core SDK and runtimes installed. +The easiest way to do this is run our `tools\Install-DotNetSdk.ps1` script. +Using the `-InstallLocality Machine` switch requires elevation but ensures +that Visual Studio will be able to load the solution even when launched from a shortcut. + +Then proceed to: + +* Create a topic branch off master (don't work directly on master). +* Implement your feature or fix your bug. Please following existing coding styles and do not introduce new ones. +* Make atomic, focused commits with good commit messages. +* Make sure you have added the necessary tests for your changes. +* Run _all_ the tests to assure nothing else was accidentally broken. + +## Submitting Changes + +* Push your changes to a topic branch in your fork of the repository. +* Send a Pull Request targeting the master branch. Note what issue/issues your patch fixes. + +Some things that will increase the chance that your pull request is accepted. + +* Following existing code conventions. +* Including unit tests that would otherwise fail without the patch, but pass after applying it. +* Updating the documentation and tests that are affected by the contribution. +* If code from elsewhere is used, proper credit and a link to the source should exist in the code comments. + Then licensing issues can be checked against LibGit2Sharp's very permissive MIT based open source license. +* Having a configured git client that converts line endings to LF. [See here.](https://help.github.com/articles/dealing-with-line-endings/). +# Additional Resources + +* [General GitHub documentation](http://help.github.com/) +* [GitHub pull request documentation](https://help.github.com/articles/using-pull-requests/) diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..2c14cc2bd --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,14 @@ + + + + true + true + true + $(DefineConstants);$(ExtraDefine) + + + + true + + + diff --git a/LICENSE.md b/LICENSE.md index 2f4d091cd..c70554345 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2011-2014 LibGit2Sharp contributors +Copyright (c) LibGit2Sharp contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Lib/.gitattributes b/Lib/.gitattributes deleted file mode 100644 index 2fa88711b..000000000 --- a/Lib/.gitattributes +++ /dev/null @@ -1,3 +0,0 @@ -* binary -.gitattributes text -binary -*.txt text -binary diff --git a/Lib/CustomBuildTasks/CustomBuildTasks.csproj b/Lib/CustomBuildTasks/CustomBuildTasks.csproj deleted file mode 100644 index 2da370371..000000000 --- a/Lib/CustomBuildTasks/CustomBuildTasks.csproj +++ /dev/null @@ -1,42 +0,0 @@ - - - - - Debug - AnyCPU - {B6138573-A4B9-44E7-83C2-8964CAF51EDA} - Library - Properties - CustomBuildTasks - CustomBuildTasks - v3.5 - 512 - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - diff --git a/Lib/CustomBuildTasks/CustomBuildTasks.dll b/Lib/CustomBuildTasks/CustomBuildTasks.dll deleted file mode 100644 index 3cd023b83..000000000 Binary files a/Lib/CustomBuildTasks/CustomBuildTasks.dll and /dev/null differ diff --git a/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs b/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs deleted file mode 100644 index 2f26ac94d..000000000 --- a/Lib/CustomBuildTasks/GenerateUniqueIdentifierTask.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.IO; -using System.Text; -using Microsoft.Build.Framework; -using Microsoft.Build.Utilities; - -namespace CustomBuildTasks -{ - public class GenerateUniqueIdentifierTask : Task - { - public override bool Execute() - { - using (FileStream fs = new FileStream(this.OutputFile, FileMode.Create, FileAccess.Write, FileShare.None)) - using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8)) - { - sw.WriteLine("using System;"); - sw.WriteLine(); - sw.WriteLine("namespace LibGit2Sharp.Core"); - sw.WriteLine("{"); - sw.WriteLine(" internal static class UniqueId"); - sw.WriteLine(" {"); - sw.WriteLine(" public const String UniqueIdentifier = \"" + Guid.NewGuid().ToString() + "\";"); - sw.WriteLine(" }"); - sw.WriteLine("}"); - } - - return true; - } - - public String OutputFile - { - get; - set; - } - } -} diff --git a/Lib/MoQ/Moq.dll b/Lib/MoQ/Moq.dll deleted file mode 100644 index bdd4235f2..000000000 Binary files a/Lib/MoQ/Moq.dll and /dev/null differ diff --git a/Lib/MoQ/Moq.license.txt b/Lib/MoQ/Moq.license.txt deleted file mode 100644 index c9216ccba..000000000 --- a/Lib/MoQ/Moq.license.txt +++ /dev/null @@ -1,39 +0,0 @@ -Copyright (c) 2007. Clarius Consulting, Manas Technology Solutions, InSTEDD -http://code.google.com/p/moq/ -All rights reserved. - -Redistribution and use in source and binary forms, -with or without modification, are permitted provided -that the following conditions are met: - - * Redistributions of source code must retain the - above copyright notice, this list of conditions and - the following disclaimer. - - * Redistributions in binary form must reproduce - the above copyright notice, this list of conditions - and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - * Neither the name of Clarius Consulting, Manas Technology Solutions or InSTEDD nor the - names of its contributors may be used to endorse - or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND -CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -SUCH DAMAGE. - -[This is the BSD license, see - http://www.opensource.org/licenses/bsd-license.php] diff --git a/Lib/MoQ/Moq.xml b/Lib/MoQ/Moq.xml deleted file mode 100644 index 160c1b516..000000000 --- a/Lib/MoQ/Moq.xml +++ /dev/null @@ -1,5449 +0,0 @@ - - - - Moq - - - - - Implements the fluent API. - - - - - The expectation will be considered only in the former condition. - - - - - - - The expectation will be considered only in the former condition. - - - - - - - - Setups the get. - - The type of the property. - The expression. - - - - - Setups the set. - - The type of the property. - The setter expression. - - - - - Setups the set. - - The setter expression. - - - - - Handle interception - - the current invocation context - shared data for the interceptor as a whole - shared data among the strategies during a single interception - InterceptionAction.Continue if further interception has to be processed, otherwise InterceptionAction.Stop - - - - Covarient interface for Mock<T> such that casts between IMock<Employee> to IMock<Person> - are possible. Only covers the covariant members of Mock<T>. - - - - - Exposes the mocked object instance. - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - - Name of the event, with the set_ or get_ prefix already removed - - - - Get an eventInfo for a given event name. Search type ancestors depth first if necessary. - Searches also in non public events. - - Name of the event, with the set_ or get_ prefix already removed - - - - Given a type return all of its ancestors, both types and interfaces. - - The type to find immediate ancestors of - - - - Defines the Callback verb and overloads. - - - - - Helper interface used to hide the base - members from the fluent API to make it much cleaner - in Visual Studio intellisense. - - - - - - - - - - - - - - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean - value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The argument type of the invoked method. - The callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback((string command) => Console.WriteLine(command)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Callback verb and overloads for callbacks on - setups that return a value. - - Mocked type. - Type of the return value of the setup. - - - - Specifies a callback to invoke when the method is called. - - The callback method to invoke. - - The following example specifies a callback to set a boolean value that can be used later: - - var called = false; - mock.Setup(x => x.Execute()) - .Callback(() => called = true) - .Returns(true); - - Note that in the case of value-returning methods, after the Callback - call you can still specify the return value. - - - - - Specifies a callback to invoke when the method is called that receives the original arguments. - - The type of the argument of the invoked method. - Callback method to invoke. - - Invokes the given callback with the concrete invocation argument value. - - Notice how the specific string argument is retrieved by simply declaring - it as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Callback(command => Console.WriteLine(command)) - .Returns(true); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2) => Console.WriteLine(arg1 + arg2)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3) => Console.WriteLine(arg1 + arg2 + arg3)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4) => Console.WriteLine(arg1 + arg2 + arg3 + arg4)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15)); - - - - - - Specifies a callback to invoke when the method is called that receives the original - arguments. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The callback method to invoke. - A reference to interface. - - Invokes the given callback with the concrete invocation arguments values. - - Notice how the specific arguments are retrieved by simply declaring - them as part of the lambda expression for the callback: - - - mock.Setup(x => x.Execute( - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>(), - It.IsAny<string>())) - .Callback((arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16) => Console.WriteLine(arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16)); - - - - - - Defines the Raises verb. - - - - - Specifies the event that will be raised - when the setup is met. - - An expression that represents an event attach or detach action. - The event arguments to pass for the raised event. - - The following example shows how to raise an event when - the setup is met: - - var mock = new Mock<IContainer>(); - - mock.Setup(add => add.Add(It.IsAny<string>(), It.IsAny<object>())) - .Raises(add => add.Added += null, EventArgs.Empty); - - - - - - Specifies the event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - A function that will build the - to pass when raising the event. - - - - - Specifies the custom event that will be raised - when the setup is matched. - - An expression that represents an event attach or detach action. - The arguments to pass to the custom delegate (non EventHandler-compatible). - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - - - - - Specifies the event that will be raised when the setup is matched. - - The expression that represents an event attach or detach action. - The function that will build the - to pass when raising the event. - The type of the first argument received by the expected invocation. - The type of the second argument received by the expected invocation. - The type of the third argument received by the expected invocation. - The type of the fourth argument received by the expected invocation. - The type of the fifth argument received by the expected invocation. - The type of the sixth argument received by the expected invocation. - The type of the seventh argument received by the expected invocation. - The type of the eighth argument received by the expected invocation. - The type of the nineth argument received by the expected invocation. - The type of the tenth argument received by the expected invocation. - The type of the eleventh argument received by the expected invocation. - The type of the twelfth argument received by the expected invocation. - The type of the thirteenth argument received by the expected invocation. - The type of the fourteenth argument received by the expected invocation. - The type of the fifteenth argument received by the expected invocation. - The type of the sixteenth argument received by the expected invocation. - - - - - Defines the Returns verb. - - Mocked type. - Type of the return value from the expression. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the method call: - - mock.Setup(x => x.Execute("ping")) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return from the method. - - The function that will calculate the return value. - - Return a calculated value when the method is called: - - mock.Setup(x => x.Execute("ping")) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the method - is executed and the value the returnValues array has at - that moment. - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the argument of the invoked method. - The function that will calculate the return value. - - Return a calculated value which is evaluated lazily at the time of the invocation. - - The lookup list can change between invocations and the setup - will return different values accordingly. Also, notice how the specific - string argument is retrieved by simply declaring it as part of the lambda - expression: - - - mock.Setup(x => x.Execute(It.IsAny<string>())) - .Returns((string command) => returnValues[command]); - - - - - - Calls the real method of the object and returns its return value. - - The value calculated by the real method of the object. - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2) => arg1 + arg2); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3) => arg1 + arg2 + arg3); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4) => arg1 + arg2 + arg3 + arg4); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5) => arg1 + arg2 + arg3 + arg4 + arg5); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15); - - - - - - Specifies a function that will calculate the value to return from the method, - retrieving the arguments for the invocation. - - The type of the first argument of the invoked method. - The type of the second argument of the invoked method. - The type of the third argument of the invoked method. - The type of the fourth argument of the invoked method. - The type of the fifth argument of the invoked method. - The type of the sixth argument of the invoked method. - The type of the seventh argument of the invoked method. - The type of the eighth argument of the invoked method. - The type of the nineth argument of the invoked method. - The type of the tenth argument of the invoked method. - The type of the eleventh argument of the invoked method. - The type of the twelfth argument of the invoked method. - The type of the thirteenth argument of the invoked method. - The type of the fourteenth argument of the invoked method. - The type of the fifteenth argument of the invoked method. - The type of the sixteenth argument of the invoked method. - The function that will calculate the return value. - Returns a calculated value which is evaluated lazily at the time of the invocation. - - - The return value is calculated from the value of the actual method invocation arguments. - Notice how the arguments are retrieved by simply declaring them as part of the lambda - expression: - - - mock.Setup(x => x.Execute( - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>(), - It.IsAny<int>())) - .Returns((string arg1, string arg2, string arg3, string arg4, string arg5, string arg6, string arg7, string arg8, string arg9, string arg10, string arg11, string arg12, string arg13, string arg14, string arg15, string arg16) => arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7 + arg8 + arg9 + arg10 + arg11 + arg12 + arg13 + arg14 + arg15 + arg16); - - - - - - Hook used to tells Castle which methods to proxy in mocked classes. - - Here we proxy the default methods Castle suggests (everything Object's methods) - plus Object.ToString(), so we can give mocks useful default names. - - This is required to allow Moq to mock ToString on proxy *class* implementations. - - - - - Extends AllMethodsHook.ShouldInterceptMethod to also intercept Object.ToString(). - - - - - The base class used for all our interface-inheriting proxies, which overrides the default - Object.ToString() behavior, to route it via the mock by default, unless overriden by a - real implementation. - - This is required to allow Moq to mock ToString on proxy *interface* implementations. - - - This is internal to Moq and should not be generally used. - - Unfortunately it must be public, due to cross-assembly visibility issues with reflection, - see github.com/Moq/moq4/issues/98 for details. - - - - - Overrides the default ToString implementation to instead find the mock for this mock.Object, - and return MockName + '.Object' as the mocked object's ToString, to make it easy to relate - mocks and mock object instances in error messages. - - - - - Defines async extension methods on IReturns. - - - - - Allows to specify the return value of an asynchronous method. - - - - - Allows to specify the exception thrown by an asynchronous method. - - - - - Language for ReturnSequence - - - - - Returns value - - - - - Throws an exception - - - - - Throws an exception - - - - - Calls original method - - - - - The first method call or member access will be the - last segment of the expression (depth-first traversal), - which is the one we have to Setup rather than FluentMock. - And the last one is the one we have to Mock.Get rather - than FluentMock. - - - - - Base class for mocks and static helper class with methods that - apply to mocked objects, such as to - retrieve a from an object instance. - - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the specification of how the mocked object should behave. - The type of the mocked object. - The mocked object created. - - - - Initializes a new instance of the class. - - - - - Retrieves the mock object for the given object instance. - - Type of the mock to retrieve. Can be omitted as it's inferred - from the object instance passed in as the instance. - The instance of the mocked object.The mock associated with the mocked object. - The received instance - was not created by Moq. - - The following example shows how to add a new setup to an object - instance which is not the original but rather - the object associated with it: - - // Typed instance, not the mock, is retrieved from some test API. - HttpContextBase context = GetMockContext(); - - // context.Request is the typed object from the "real" API - // so in order to add a setup to it, we need to get - // the mock that "owns" it - Mock<HttpRequestBase> request = Mock.Get(context.Request); - mock.Setup(req => req.AppRelativeCurrentExecutionFilePath) - .Returns(tempUrl); - - - - - - Returns the mocked object value. - - - - - Verifies that all verifiable expectations have been met. - - This example sets up an expectation and marks it as verifiable. After - the mock is used, a Verify() call is issued on the mock - to ensure the method in the setup was invoked: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Verifiable().Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory. - this.Verify(); - - Not all verifiable expectations were met. - - - - Verifies all expectations regardless of whether they have - been flagged as verifiable. - - This example sets up an expectation without marking it as verifiable. After - the mock is used, a call is issued on the mock - to ensure that all expectations are met: - - var mock = new Mock<IWarehouse>(); - this.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - ... - // other test code - ... - // Will throw if the test code has didn't call HasInventory, even - // that expectation was not marked as verifiable. - this.VerifyAll(); - - At least one expectation was not met. - - - - Gets the interceptor target for the given expression and root mock, - building the intermediate hierarchy of mock objects if necessary. - - - - - Raises the associated event with the given - event argument data. - - - - - Raises the associated event with the given - event argument data. - - - - - Adds an interface implementation to the mock, - allowing setups to be specified for it. - - This method can only be called before the first use - of the mock property, at which - point the runtime type has already been generated - and no more interfaces can be added to it. - - Also, must be an - interface and not a class, which must be specified - when creating the mock instead. - - - The mock type - has already been generated by accessing the property. - - The specified - is not an interface. - - The following example creates a mock for the main interface - and later adds to it to verify - it's called by the consumer code: - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - // add IDisposable interface - var disposable = mock.As<IDisposable>(); - disposable.Setup(d => d.Dispose()).Verifiable(); - - Type of interface to cast the mock to. - - - - - - - Behavior of the mock, according to the value set in the constructor. - - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocked object instance. - - - - - Retrieves the type of the mocked object, its generic type argument. - This is used in the auto-mocking of hierarchy access. - - - - - If this is a mock of a delegate, this property contains the method - on the autogenerated interface so that we can convert setup + verify - expressions on the delegate into expressions on the interface proxy. - - - - - Allows to check whether expression conversion to the - must be performed on the mock, without causing unnecessarily early initialization of - the mock instance, which breaks As{T}. - - - - - Specifies the class that will determine the default - value to return when invocations are made that - have no setups and need to return a default - value (for loose mocks). - - - - - Exposes the list of extra interfaces implemented by the mock. - - - - - Utility repository class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the repository constructor) and later verifying each - mock can become repetitive and tedious. - - This repository class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var repository = new MockRepository(MockBehavior.Strict); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - repository.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the repository - to create loose mocks and later verify only verifiable setups: - - var repository = new MockRepository(MockBehavior.Loose); - - var foo = repository.Create<IFoo>(); - var bar = repository.Create<IBar>(); - - // this setup will be verified when we verify the repository - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the repository - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - repository.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the repository with a - default strict behavior, overriding that default for a - specific mock: - - var repository = new MockRepository(MockBehavior.Strict); - - // this particular one we want loose - var foo = repository.Create<IFoo>(MockBehavior.Loose); - var bar = repository.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - repository.Verify(); - - - - - - - Utility factory class to use to construct multiple - mocks when consistent verification is - desired for all of them. - - - If multiple mocks will be created during a test, passing - the desired (if different than the - or the one - passed to the factory constructor) and later verifying each - mock can become repetitive and tedious. - - This factory class helps in that scenario by providing a - simplified creation of multiple mocks with a default - (unless overriden by calling - ) and posterior verification. - - - - The following is a straightforward example on how to - create and automatically verify strict mocks using a : - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // no need to call Verifiable() on the setup - // as we'll be validating all of them anyway. - foo.Setup(f => f.Do()); - bar.Setup(b => b.Redo()); - - // exercise the mocks here - - factory.VerifyAll(); - // At this point all setups are already checked - // and an optional MockException might be thrown. - // Note also that because the mocks are strict, any invocation - // that doesn't have a matching setup will also throw a MockException. - - The following examples shows how to setup the factory - to create loose mocks and later verify only verifiable setups: - - var factory = new MockFactory(MockBehavior.Loose); - - var foo = factory.Create<IFoo>(); - var bar = factory.Create<IBar>(); - - // this setup will be verified when we verify the factory - foo.Setup(f => f.Do()).Verifiable(); - - // this setup will NOT be verified - foo.Setup(f => f.Calculate()); - - // this setup will be verified when we verify the factory - bar.Setup(b => b.Redo()).Verifiable(); - - // exercise the mocks here - // note that because the mocks are Loose, members - // called in the interfaces for which no matching - // setups exist will NOT throw exceptions, - // and will rather return default values. - - factory.Verify(); - // At this point verifiable setups are already checked - // and an optional MockException might be thrown. - - The following examples shows how to setup the factory with a - default strict behavior, overriding that default for a - specific mock: - - var factory = new MockFactory(MockBehavior.Strict); - - // this particular one we want loose - var foo = factory.Create<IFoo>(MockBehavior.Loose); - var bar = factory.Create<IBar>(); - - // specify setups - - // exercise the mocks here - - factory.Verify(); - - - - - - - Initializes the factory with the given - for newly created mocks from the factory. - - The behavior to use for mocks created - using the factory method if not overriden - by using the overload. - - - - Creates a new mock with the default - specified at factory construction time. - - Type to mock. - A new . - - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(); - // use mock on tests - - factory.VerifyAll(); - - - - - - Creates a new mock with the default - specified at factory construction time and with the - the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Constructor arguments for mocked classes. - A new . - - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>("Foo", 25, true); - // use mock on tests - - factory.Verify(); - - - - - - Creates a new mock with the given . - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory: - - var factory = new MockFactory(MockBehavior.Strict); - - var foo = factory.Create<IFoo>(MockBehavior.Loose); - - - - - - Creates a new mock with the given - and with the the given constructor arguments for the class. - - - The mock will try to find the best match constructor given the - constructor arguments, and invoke that to initialize the instance. - This applies only to classes, not interfaces. - - Type to mock. - Behavior to use for the mock, which overrides - the default behavior specified at factory construction time. - Constructor arguments for mocked classes. - A new . - - The following example shows how to create a mock with a different - behavior to that specified as the default for the factory, passing - constructor arguments: - - var factory = new MockFactory(MockBehavior.Default); - - var mock = factory.Create<MyBase>(MockBehavior.Strict, "Foo", 25, true); - - - - - - Implements creation of a new mock within the factory. - - Type to mock. - The behavior for the new mock. - Optional arguments for the construction of the mock. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Verifies all verifiable expectations on all mocks created - by this factory. - - - One or more mocks had expectations that were not satisfied. - - - - Invokes for each mock - in , and accumulates the resulting - that might be - thrown from the action. - - The action to execute against - each mock. - - - - Whether the base member virtual implementation will be called - for mocked classes if no setup is matched. Defaults to . - - - - - Specifies the behavior to use when returning default values for - unexpected invocations on loose mocks. - - - - - Gets the mocks that have been created by this factory and - that will get verified together. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Initializes the repository with the given - for newly created mocks from the repository. - - The behavior to use for mocks created - using the repository method if not overriden - by using the overload. - - - - A that returns an empty default value - for invocations that do not have setups or return values, with loose mocks. - This is the default behavior for a mock. - - - - - Interface to be implemented by classes that determine the - default value of non-expected invocations. - - - - - Defines the default value to return in all the methods returning . - The type of the return value.The value to set as default. - - - - Provides a value for the given member and arguments. - - The member to provide a default value for. - - - - - The intention of is to create a more readable - string representation for the failure message. - - - - - Implements the fluent API. - - - - - Defines the Throws verb. - - - - - Specifies the exception to throw when the method is invoked. - - Exception instance to throw. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws(new ArgumentException()); - - - - - - Specifies the type of exception to throw when the method is invoked. - - Type of exception to instantiate and throw when the setup is matched. - - This example shows how to throw an exception when the method is - invoked with an empty string argument: - - mock.Setup(x => x.Execute("")) - .Throws<ArgumentException>(); - - - - - - Implements the fluent API. - - - - - Defines occurrence members to constraint setups. - - - - - The expected invocation can happen at most once. - - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMostOnce(); - - - - - - The expected invocation can happen at most specified number of times. - - The number of times to accept calls. - - - var mock = new Mock<ICommand>(); - mock.Setup(foo => foo.Execute("ping")) - .AtMost( 5 ); - - - - - - Defines the Verifiable verb. - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable(); - - - - - - Marks the expectation as verifiable, meaning that a call - to will check if this particular - expectation was met, and specifies a message for failures. - - - The following example marks the expectation as verifiable: - - mock.Expect(x => x.Execute("ping")) - .Returns(true) - .Verifiable("Ping should be executed always!"); - - - - - - Implements the fluent API. - - - - - We need this non-generics base class so that - we can use from - generic code. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property getter setups. - - - Mocked type. - Type of the property. - - - - Specifies a callback to invoke when the property is retrieved. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupGet(x => x.Suspended) - .Callback(() => called = true) - .Returns(true); - - - - - - Implements the fluent API. - - - - - Defines the Returns verb for property get setups. - - Mocked type. - Type of the property. - - - - Specifies the value to return. - - The value to return, or . - - Return a true value from the property getter call: - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a function that will calculate the value to return for the property. - - The function that will calculate the return value. - - Return a calculated value when the property is retrieved: - - mock.SetupGet(x => x.Suspended) - .Returns(() => returnValues[0]); - - The lambda expression to retrieve the return value is lazy-executed, - meaning that its value may change depending on the moment the property - is retrieved and the value the returnValues array has at - that moment. - - - - - Calls the real property of the object and returns its return value. - - The value calculated by the real property of the object. - - - - Implements the fluent API. - - - - - Provides additional methods on mocks. - - - Those methods are useful for Testeroids support. - - - - - Resets the calls previously made on the specified mock. - - The mock whose calls need to be reset. - - - - Helper class to setup a full trace between many mocks - - - - - Initialize a trace setup - - - - - Allow sequence to be repeated - - - - - define nice api - - - - - Perform an expectation in the trace. - - - - - Marks a method as a matcher, which allows complete replacement - of the built-in class with your own argument - matching rules. - - - This feature has been deprecated in favor of the new - and simpler . - - - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - - There are two parts of a matcher: the compiler matcher - and the runtime matcher. - - - Compiler matcher - Used to satisfy the compiler requirements for the - argument. Needs to be a method optionally receiving any arguments - you might need for the matching, but with a return type that - matches that of the argument. - - Let's say I want to match a lists of orders that contains - a particular one. I might create a compiler matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - Note that the return value from the compiler matcher is irrelevant. - This method will never be called, and is just used to satisfy the - compiler and to signal Moq that this is not a method that we want - to be invoked at runtime. - - - - Runtime matcher - - The runtime matcher is the one that will actually perform evaluation - when the test is run, and is defined by convention to have the - same signature as the compiler matcher, but where the return - value is the first argument to the call, which contains the - object received by the actual invocation at runtime: - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - - At runtime, the mocked method will be invoked with a specific - list of orders. This value will be passed to this runtime - matcher as the first argument, while the second argument is the - one specified in the setup (x.Save(Orders.Contains(order))). - - The boolean returned determines whether the given argument has been - matched. If all arguments to the expected method are matched, then - the setup matches and is evaluated. - - - - - - Using this extensible infrastructure, you can easily replace the entire - set of matchers with your own. You can also avoid the - typical (and annoying) lengthy expressions that result when you have - multiple arguments that use generics. - - - The following is the complete example explained above: - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return null; - } - - public static bool Contains(IEnumerable<Order> orders, Order order) - { - return orders.Contains(order); - } - } - - And the concrete test using this matcher: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - // use mock, invoke Save, and have the matcher filter. - - - - - - Provides a mock implementation of . - - Any interface type can be used for mocking, but for classes, only abstract and virtual members can be mocked. - - The behavior of the mock with regards to the setups and the actual calls is determined - by the optional that can be passed to the - constructor. - - Type to mock, which can be an interface or a class. - The following example shows establishing setups with specific values - for method invocations: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - mock.Setup(x => x.HasInventory(TALISKER, 50)).Returns(true); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.True(order.IsFilled); - - The following example shows how to use the class - to specify conditions for arguments instead of specific values: - - // Arrange - var order = new Order(TALISKER, 50); - var mock = new Mock<IWarehouse>(); - - // shows how to expect a value within a range - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - // shows how to throw for unexpected calls. - mock.Setup(x => x.Remove( - It.IsAny<string>(), - It.IsAny<int>())) - .Throws(new InvalidOperationException()); - - // Act - order.Fill(mock.Object); - - // Assert - Assert.False(order.IsFilled); - - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Ctor invoked by AsTInterface exclusively. - - - - - Initializes an instance of the mock with default behavior. - - var mock = new Mock<IFormatProvider>(); - - - - - Initializes an instance of the mock with default behavior and with - the given constructor arguments for the class. (Only valid when is a class) - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only for classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Optional constructor arguments if the mocked type is a class. - - - - Initializes an instance of the mock with the specified behavior. - - var mock = new Mock<IFormatProvider>(MockBehavior.Relaxed); - Behavior of the mock. - - - - Initializes an instance of the mock with a specific behavior with - the given constructor arguments for the class. - - The mock will try to find the best match constructor given the constructor arguments, and invoke that - to initialize the instance. This applies only to classes, not interfaces. - - var mock = new Mock<MyProvider>(someArgument, 25); - Behavior of the mock.Optional constructor arguments if the mocked type is a class. - - - - Returns the name of the mock - - - - - Returns the mocked object value. - - - - - Specifies a setup on the mocked type for a call to - to a void method. - - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the expected method invocation. - - var mock = new Mock<IProcessor>(); - mock.Setup(x => x.Execute("ping")); - - - - - - Specifies a setup on the mocked type for a call to - to a value returning method. - Type of the return value. Typically omitted as it can be inferred from the expression. - If more than one setup is specified for the same method or property, - the latest one wins and is the one that will be executed. - Lambda expression that specifies the method invocation. - - mock.Setup(x => x.HasInventory("Talisker", 50)).Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property getter. - - If more than one setup is set for the same property getter, - the latest one wins and is the one that will be executed. - Type of the property. Typically omitted as it can be inferred from the expression.Lambda expression that specifies the property getter. - - mock.SetupGet(x => x.Suspended) - .Returns(true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - This overloads allows the use of a callback already - typed for the property type. - - Type of the property. Typically omitted as it can be inferred from the expression.The Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies a setup on the mocked type for a call to - to a property setter. - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - Lambda expression that sets a property to a value. - - mock.SetupSet(x => x.Suspended = true); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.Stub(v => v.Value); - - After the Stub call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - - v.Value = 5; - Assert.Equal(5, v.Value); - - - - - - Specifies that the given property should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. This overload - allows setting the initial value for the property. (this is also - known as "stubbing"). - - Type of the property, inferred from the property - expression (does not need to be specified). - Property expression to stub.Initial value for the property. - If you have an interface with an int property Value, you might - stub it using the following straightforward call: - - var mock = new Mock<IHaveValue>(); - mock.SetupProperty(v => v.Value, 5); - - After the SetupProperty call has been issued, setting and - retrieving the object value will behave as expected: - - IHaveValue v = mock.Object; - // Initial value was stored - Assert.Equal(5, v.Value); - - // New value set which changes the initial value - v.Value = 6; - Assert.Equal(6, v.Value); - - - - - - Specifies that the all properties on the mock should have "property behavior", - meaning that setting its value will cause it to be saved and - later returned when the property is requested. (this is also - known as "stubbing"). The default value for each property will be the - one generated as specified by the property for the mock. - - If the mock is set to , - the mocked default values will also get all properties setup recursively. - - - - - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IProcessor>(); - // exercise mock - //... - // Will throw if the test code didn't call Execute with a "ping" string argument. - mock.Verify(proc => proc.Execute("ping")); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock, - specifying a failure error message. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails. - - - - Verifies that a specific invocation matching the given expression was performed on the mock. Use - in conjuntion with the default . - - This example assumes that the mock has been used, and later we want to verify that a given - invocation with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50)); - - The invocation was not performed on the mock.Expression to verify.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock. Use in conjuntion - with the default . - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't call HasInventory. - mock.Verify(warehouse => warehouse.HasInventory(TALISKER, 50), "When filling orders, inventory has to be checked"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a specific invocation matching the given - expression was performed on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - Expression to verify.The number of times a method is allowed to be called.Message to show if verification fails.Type of return value from the expression. - - - - Verifies that a property was read on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was retrieved from it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't retrieve the IsClosed property. - mock.VerifyGet(warehouse => warehouse.IsClosed); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was read on the mock, specifying a failure - error message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - - Verifies that a property was set on the mock. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true); - - The invocation was not performed on the mock.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - This example assumes that the mock has been used, - and later we want to verify that a given property - was set on it: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed = true, "Warehouse should always be closed after the action"); - - The invocation was not performed on the mock.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Verifies that a property was set on the mock, specifying - a failure message. - - The invocation was not call the times specified by - . - The number of times a method is allowed to be called.Expression to verify.Message to show if verification fails. - - - - Raises the event referenced in using - the given argument. - - The argument is - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a event: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.PropertyChanged -= null, new PropertyChangedEventArgs("Name")); - - - This example shows how to invoke an event with a custom event arguments - class in a view that will cause its corresponding presenter to - react by changing its state: - - var mockView = new Mock<IOrdersView>(); - var presenter = new OrdersPresenter(mockView.Object); - - // Check that the presenter has no selection by default - Assert.Null(presenter.SelectedOrder); - - // Raise the event with a specific arguments data - mockView.Raise(v => v.SelectionChanged += null, new OrderEventArgs { Order = new Order("moq", 500) }); - - // Now the presenter reacted to the event, and we have a selected order - Assert.NotNull(presenter.SelectedOrder); - Assert.Equal("moq", presenter.SelectedOrder.ProductName); - - - - - - Raises the event referenced in using - the given argument for a non-EventHandler typed event. - - The arguments are - invalid for the target event invocation, or the is - not an event attach or detach expression. - - The following example shows how to raise a custom event that does not adhere to - the standard EventHandler: - - var mock = new Mock<IViewModel>(); - - mock.Raise(x => x.MyEvent -= null, "Name", bool, 25); - - - - - - Exposes the mocked object instance. - - - - - Allows naming of your mocks, so they can be easily identified in error messages (e.g. from failed assertions). - - - - - - - - Provides legacy API members as extensions so that - existing code continues to compile, but new code - doesn't see then. - - - - - Obsolete. - - - - - Obsolete. - - - - - Obsolete. - - - - - Provides additional methods on mocks. - - - Provided as extension methods as they confuse the compiler - with the overloads taking Action. - - - - - Specifies a setup on the mocked type for a call to - to a property setter, regardless of its value. - - - If more than one setup is set for the same property setter, - the latest one wins and is the one that will be executed. - - Type of the property. Typically omitted as it can be inferred from the expression. - Type of the mock. - The target mock for the setup. - Lambda expression that specifies the property setter. - - - mock.SetupSet(x => x.Suspended); - - - - This method is not legacy, but must be on an extension method to avoid - confusing the compiler with the new Action syntax. - - - - - Verifies that a property has been set on the mock, regarless of its value. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - Expression to verify. - Message to show if verification fails. - The mock instance. - Mocked type. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Verifies that a property has been set on the mock, regardless - of the value but only the specified number of times, and specifying a failure - error message. - - - This example assumes that the mock has been used, - and later we want to verify that a given invocation - with specific parameters was performed: - - var mock = new Mock<IWarehouse>(); - // exercise mock - //... - // Will throw if the test code didn't set the IsClosed property. - mock.VerifySet(warehouse => warehouse.IsClosed); - - - The invocation was not performed on the mock. - The invocation was not call the times specified by - . - The mock instance. - Mocked type. - The number of times a method is allowed to be called. - Message to show if verification fails. - Expression to verify. - Type of the property to verify. Typically omitted as it can - be inferred from the expression's return type. - - - - Helper for sequencing return values in the same method. - - - - - Return a sequence of values, once per call. - - - - - Casts the expression to a lambda expression, removing - a cast if there's any. - - - - - Casts the body of the lambda expression to a . - - If the body is not a method call. - - - - Converts the body of the lambda expression into the referenced by it. - - - - - Checks whether the body of the lambda expression is a property access. - - - - - Checks whether the expression is a property access. - - - - - Checks whether the body of the lambda expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Checks whether the expression is a property indexer, which is true - when the expression is an whose - has - equal to . - - - - - Creates an expression that casts the given expression to the - type. - - - - - TODO: remove this code when https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=331583 - is fixed. - - - - - Extracts, into a common form, information from a - around either a (for a normal method call) - or a (for a delegate invocation). - - - - - Tests if a type is a delegate type (subclasses ). - - - - - Provides partial evaluation of subtrees, whenever they can be evaluated locally. - - Matt Warren: http://blogs.msdn.com/mattwar - Documented by InSTEDD: http://www.instedd.org - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A function that decides whether a given expression - node can be part of the local function. - A new tree with sub-trees evaluated and replaced. - - - - Performs evaluation and replacement of independent sub-trees - - The root of the expression tree. - A new tree with sub-trees evaluated and replaced. - - - - Evaluates and replaces sub-trees when first candidate is reached (top-down) - - - - - Performs bottom-up analysis to determine which nodes can possibly - be part of an evaluated sub-tree. - - - - - Ensures the given is not null. - Throws otherwise. - - - - - Ensures the given string is not null or empty. - Throws in the first case, or - in the latter. - - - - - Checks an argument to ensure it is in the specified range including the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Checks an argument to ensure it is in the specified range excluding the edges. - - Type of the argument to check, it must be an type. - - The expression containing the name of the argument. - The argument value to check. - The minimun allowed value for the argument. - The maximun allowed value for the argument. - - - - Implemented by all generated mock object instances. - - - - - Implemented by all generated mock object instances. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Reference the Mock that contains this as the mock.Object value. - - - - - Implements the actual interception and method invocation for - all mocks. - - - - - Implements the fluent API. - - - - - Defines the Callback verb for property setter setups. - - Type of the property. - - - - Specifies a callback to invoke when the property is set that receives the - property value being set. - - Callback method to invoke. - - Invokes the given callback with the property value being set. - - mock.SetupSet(x => x.Suspended) - .Callback((bool state) => Console.WriteLine(state)); - - - - - - Allows the specification of a matching condition for an - argument in a method invocation, rather than a specific - argument value. "It" refers to the argument being matched. - - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate. - - - - - Matches any value of the given type. - - Typically used when the actual argument value for a method - call is not relevant. - - - // Throws an exception for a call to Remove with any string value. - mock.Setup(x => x.Remove(It.IsAny<string>())).Throws(new InvalidOperationException()); - - Type of the value. - - - - Matches any value of the given type, except null. - Type of the value. - - - - Matches any value that satisfies the given predicate. - Type of the argument to check.The predicate used to match the method argument. - Allows the specification of a predicate to perform matching - of method call arguments. - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Setup(x => x.Do(It.Is<int>(i => i % 2 == 0))) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Setup(x => x.GetUser(It.Is<int>(i => i < 0))) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - Type of the argument to check.The lower bound of the range.The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsInRange(0, 100, Range.Inclusive))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with value from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(values))) - .Returns(false); - - - - - - Matches any value that is present in the sequence specified. - Type of the argument to check.The sequence of possible values. - The following example shows how to expect a method call - with an integer argument with a value of 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsIn(1, 2, 3))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument with value not found from a list. - - var values = new List<int> { 1, 2, 3 }; - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(values))) - .Returns(false); - - - - - - Matches any value that is not found in the sequence specified. - Type of the argument to check.The sequence of disallowed values. - The following example shows how to expect a method call - with an integer argument of any value except 1, 2, or 3. - - mock.Setup(x => x.HasInventory( - It.IsAny<string>(), - It.IsNotIn(1, 2, 3))) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+"))).Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - The pattern to use to match the string argument value.The options used to interpret the pattern. - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Setup(x => x.Check(It.IsRegex("[a-z]+", RegexOptions.IgnoreCase))).Returns(1); - - - - - - Matcher to treat static functions as matchers. - - mock.Setup(x => x.StringMethod(A.MagicString())); - - public static class A - { - [Matcher] - public static string MagicString() { return null; } - public static bool MagicString(string arg) - { - return arg == "magic"; - } - } - - Will succeed if: mock.Object.StringMethod("magic"); - and fail with any other call. - - - - - Options to customize the behavior of the mock. - - - - - Causes the mock to always throw - an exception for invocations that don't have a - corresponding setup. - - - - - Will never throw exceptions, returning default - values when necessary (null for reference types, - zero for value types or empty enumerables and arrays). - - - - - Default mock behavior, which equals . - - - - - Exception thrown by mocks when setups are not matched, - the mock is not properly setup, etc. - - - A distinct exception type is provided so that exceptions - thrown by the mock can be differentiated in tests that - expect other exceptions to be thrown (i.e. ArgumentException). - - Richer exception hierarchy/types are not provided as - tests typically should not catch or expect exceptions - from the mocks. These are typically the result of changes - in the tested class or its collaborators implementation, and - result in fixes in the mock setup so that they dissapear and - allow the test to pass. - - - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Supports the serialization infrastructure. - - Serialization information. - Streaming context. - - - - Indicates whether this exception is a verification fault raised by Verify() - - - - - Made internal as it's of no use for - consumers, but it's important for - our own tests. - - - - - Used by the mock factory to accumulate verification - failures. - - - - - Supports the serialization infrastructure. - - - - - A strongly-typed resource class, for looking up localized strings, etc. - - - - - Returns the cached ResourceManager instance used by this class. - - - - - Overrides the current thread's CurrentUICulture property for all - resource lookups using this strongly typed resource class. - - - - - Looks up a localized string similar to Mock type has already been initialized by accessing its Object property. Adding interfaces must be done before that.. - - - - - Looks up a localized string similar to Value cannot be an empty string.. - - - - - Looks up a localized string similar to Can only add interfaces to the mock.. - - - - - Looks up a localized string similar to Can't set return value for void method {0}.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for delegate mocks.. - - - - - Looks up a localized string similar to Constructor arguments cannot be passed for interface mocks.. - - - - - Looks up a localized string similar to A matching constructor for the given arguments was not found on the mocked type.. - - - - - Looks up a localized string similar to Could not locate event for attach or detach method {0}.. - - - - - Looks up a localized string similar to Expression {0} involves a field access, which is not supported. Use properties instead.. - - - - - Looks up a localized string similar to Type to mock must be an interface or an abstract or non-sealed class. . - - - - - Looks up a localized string similar to Cannot retrieve a mock with the given object type {0} as it's not the main type of the mock or any of its additional interfaces. - Please cast the argument to one of the supported types: {1}. - Remember that there's no generics covariance in the CLR, so your object must be one of these types in order for the call to succeed.. - - - - - Looks up a localized string similar to The equals ("==" or "=" in VB) and the conditional 'and' ("&&" or "AndAlso" in VB) operators are the only ones supported in the query specification expression. Unsupported expression: {0}. - - - - - Looks up a localized string similar to LINQ method '{0}' not supported.. - - - - - Looks up a localized string similar to Expression contains a call to a method which is not virtual (overridable in VB) or abstract. Unsupported expression: {0}. - - - - - Looks up a localized string similar to Member {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Method {0}.{1} is public. Use strong-typed Expect overload instead: - mock.Setup(x => x.{1}()); - . - - - - - Looks up a localized string similar to {0} invocation failed with mock behavior {1}. - {2}. - - - - - Looks up a localized string similar to Expected only {0} calls to {1}.. - - - - - Looks up a localized string similar to Expected only one call to {0}.. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at least once, but was never performed: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most {3} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock at most once, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Exclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock between {2} and {3} times (Inclusive), but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock exactly {2} times, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock should never have been performed, but was {4} times: {1}. - - - - - Looks up a localized string similar to {0} - Expected invocation on the mock once, but was {4} times: {1}. - - - - - Looks up a localized string similar to All invocations on the mock must have a corresponding setup.. - - - - - Looks up a localized string similar to Object instance was not created by Moq.. - - - - - Looks up a localized string similar to Out expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a getter.. - - - - - Looks up a localized string similar to Property {0}.{1} does not exist.. - - - - - Looks up a localized string similar to Property {0}.{1} is write-only.. - - - - - Looks up a localized string similar to Property {0}.{1} is read-only.. - - - - - Looks up a localized string similar to Property {0}.{1} does not have a setter.. - - - - - Looks up a localized string similar to Cannot raise a mocked event unless it has been associated (attached) to a concrete event in a mocked object.. - - - - - Looks up a localized string similar to Ref expression must evaluate to a constant value.. - - - - - Looks up a localized string similar to Invocation needs to return a value and therefore must have a corresponding setup that provides it.. - - - - - Looks up a localized string similar to A lambda expression is expected as the argument to It.Is<T>.. - - - - - Looks up a localized string similar to Invocation {0} should not have been made.. - - - - - Looks up a localized string similar to Expression is not a method invocation: {0}. - - - - - Looks up a localized string similar to Expression is not a property access: {0}. - - - - - Looks up a localized string similar to Expression is not a property setter invocation.. - - - - - Looks up a localized string similar to Expression references a method that does not belong to the mocked object: {0}. - - - - - Looks up a localized string similar to Invalid setup on a non-virtual (overridable in VB) member: {0}. - - - - - Looks up a localized string similar to Type {0} does not implement required interface {1}. - - - - - Looks up a localized string similar to Type {0} does not from required type {1}. - - - - - Looks up a localized string similar to To specify a setup for public property {0}.{1}, use the typed overloads, such as: - mock.Setup(x => x.{1}).Returns(value); - mock.SetupGet(x => x.{1}).Returns(value); //equivalent to previous one - mock.SetupSet(x => x.{1}).Callback(callbackDelegate); - . - - - - - Looks up a localized string similar to Unsupported expression: {0}. - - - - - Looks up a localized string similar to Only property accesses are supported in intermediate invocations on a setup. Unsupported expression {0}.. - - - - - Looks up a localized string similar to Expression contains intermediate property access {0}.{1} which is of type {2} and cannot be mocked. Unsupported expression {3}.. - - - - - Looks up a localized string similar to Setter expression cannot use argument matchers that receive parameters.. - - - - - Looks up a localized string similar to Member {0} is not supported for protected mocking.. - - - - - Looks up a localized string similar to Setter expression can only use static custom matchers.. - - - - - Looks up a localized string similar to The following setups were not matched: - {0}. - - - - - Looks up a localized string similar to Invalid verify on a non-virtual (overridable in VB) member: {0}. - - - - - Allows setups to be specified for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Specifies a setup for a void method invocation with the given - , optionally specifying arguments for the method call. - - The name of the void method to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a setup for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The return type of the method or property. - - - - Specifies a setup for an invocation on a property getter with the given - . - - The name of the property. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The name of the property. - The property value. If argument matchers are used, - remember to use rather than . - The type of the property. - - - - Specifies a verify for a void method with the given , - optionally specifying arguments for the method call. Use in conjuntion with the default - . - - The invocation was not call the times specified by - . - The name of the void method to be verified. - The number of times a method is allowed to be called. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - - - - Specifies a verify for an invocation on a property or a non void method with the given - , optionally specifying arguments for the method call. - - The invocation was not call the times specified by - . - The name of the method or property to be invoked. - The optional arguments for the invocation. If argument matchers are used, - remember to use rather than . - The number of times a method is allowed to be called. - The type of return value from the expression. - - - - Specifies a verify for an invocation on a property getter with the given - . - The invocation was not call the times specified by - . - - The name of the property. - The number of times a method is allowed to be called. - The type of the property. - - - - Specifies a setup for an invocation on a property setter with the given - . - - The invocation was not call the times specified by - . - The name of the property. - The number of times a method is allowed to be called. - The property value. - The type of the property. If argument matchers are used, - remember to use rather than . - - - - Allows the specification of a matching condition for an - argument in a protected member setup, rather than a specific - argument value. "ItExpr" refers to the argument being matched. - - - Use this variant of argument matching instead of - for protected setups. - This class allows the setup to match a method invocation - with an arbitrary value, with a value in a specified range, or - even one that matches a given predicate, or null. - - - - - Matches a null value of the given type. - - - Required for protected mocks as the null value cannot be used - directly as it prevents proper method overload selection. - - - - // Throws an exception for a call to Remove with a null string value. - mock.Protected() - .Setup("Remove", ItExpr.IsNull<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value of the given type. - - - Typically used when the actual argument value for a method - call is not relevant. - - - - // Throws an exception for a call to Remove with any string value. - mock.Protected() - .Setup("Remove", ItExpr.IsAny<string>()) - .Throws(new InvalidOperationException()); - - - Type of the value. - - - - Matches any value that satisfies the given predicate. - - Type of the argument to check. - The predicate used to match the method argument. - - Allows the specification of a predicate to perform matching - of method call arguments. - - - This example shows how to return the value 1 whenever the argument to the - Do method is an even number. - - mock.Protected() - .Setup("Do", ItExpr.Is<int>(i => i % 2 == 0)) - .Returns(1); - - This example shows how to throw an exception if the argument to the - method is a negative number: - - mock.Protected() - .Setup("GetUser", ItExpr.Is<int>(i => i < 0)) - .Throws(new ArgumentException()); - - - - - - Matches any value that is in the range specified. - - Type of the argument to check. - The lower bound of the range. - The upper bound of the range. - The kind of range. See . - - The following example shows how to expect a method call - with an integer argument within the 0..100 range. - - mock.Protected() - .Setup("HasInventory", - ItExpr.IsAny<string>(), - ItExpr.IsInRange(0, 100, Range.Inclusive)) - .Returns(false); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+")) - .Returns(1); - - - - - - Matches a string argument if it matches the given regular expression pattern. - - The pattern to use to match the string argument value. - The options used to interpret the pattern. - - The following example shows how to expect a call to a method where the - string argument matches the given regular expression, in a case insensitive way: - - mock.Protected() - .Setup("Check", ItExpr.IsRegex("[a-z]+", RegexOptions.IgnoreCase)) - .Returns(1); - - - - - - Enables the Protected() method on , - allowing setups to be set for protected members by using their - name as a string, rather than strong-typing them which is not possible - due to their visibility. - - - - - Enable protected setups for the mock. - - Mocked object type. Typically omitted as it can be inferred from the mock instance. - The mock to set the protected setups on. - - - - - - - - - - - - Kind of range to use in a filter specified through - . - - - - - The range includes the to and - from values. - - - - - The range does not include the to and - from values. - - - - - Determines the way default values are generated - calculated for loose mocks. - - - - - Default behavior, which generates empty values for - value types (i.e. default(int)), empty array and - enumerables, and nulls for all other reference types. - - - - - Whenever the default value generated by - is null, replaces this value with a mock (if the type - can be mocked). - - - For sealed classes, a null value will be generated. - - - - - A default implementation of IQueryable for use with QueryProvider - - - - - The is a - static method that returns an IQueryable of Mocks of T which is used to - apply the linq specification to. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - - See also . - - - - - Provided for the sole purpose of rendering the delegate passed to the - matcher constructor if no friendly render lambda is provided. - - - - - Initializes the match with the condition that - will be checked in order to match invocation - values. - The condition to match against actual values. - - - - - - - - - This method is used to set an expression as the last matcher invoked, - which is used in the SetupSet to allow matchers in the prop = value - delegate expression. This delegate is executed in "fluent" mode in - order to capture the value being set, and construct the corresponding - methodcall. - This is also used in the MatcherFactory for each argument expression. - This method ensures that when we execute the delegate, we - also track the matcher that was invoked, so that when we create the - methodcall we build the expression using it, rather than the null/default - value returned from the actual invocation. - - - - - Allows creation custom value matchers that can be used on setups and verification, - completely replacing the built-in class with your own argument - matching rules. - Type of the value to match. - The argument matching is used to determine whether a concrete - invocation in the mock matches a given setup. This - matching mechanism is fully extensible. - - Creating a custom matcher is straightforward. You just need to create a method - that returns a value from a call to with - your matching condition and optional friendly render expression: - - [Matcher] - public Order IsBigOrder() - { - return Match<Order>.Create( - o => o.GrandTotal >= 5000, - /* a friendly expression to render on failures */ - () => IsBigOrder()); - } - - This method can be used in any mock setup invocation: - - mock.Setup(m => m.Submit(IsBigOrder()).Throws<UnauthorizedAccessException>(); - - At runtime, Moq knows that the return value was a matcher (note that the method MUST be - annotated with the [Matcher] attribute in order to determine this) and - evaluates your predicate with the actual value passed into your predicate. - - Another example might be a case where you want to match a lists of orders - that contains a particular one. You might create matcher like the following: - - - public static class Orders - { - [Matcher] - public static IEnumerable<Order> Contains(Order order) - { - return Match<IEnumerable<Order>>.Create(orders => orders.Contains(order)); - } - } - - Now we can invoke this static method instead of an argument in an - invocation: - - var order = new Order { ... }; - var mock = new Mock<IRepository<Order>>(); - - mock.Setup(x => x.Save(Orders.Contains(order))) - .Throws<ArgumentException>(); - - - - - - Tracks the current mock and interception context. - - - - - Having an active fluent mock context means that the invocation - is being performed in "trial" mode, just to gather the - target method and arguments that need to be matched later - when the actual invocation is made. - - - - - A that returns an empty default value - for non-mockeable types, and mocks for all other types (interfaces and - non-sealed classes) that can be mocked. - - - - - Allows querying the universe of mocks for those that behave - according to the LINQ query specification. - - - This entry-point into Linq to Mocks is the only one in the root Moq - namespace to ease discovery. But to get all the mocking extension - methods on Object, a using of Moq.Linq must be done, so that the - polluting of the intellisense for all objects is an explicit opt-in. - - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The type of the mocked object to query. - - - - Access the universe of mocks of the given type, to retrieve those - that behave according to the LINQ query specification. - - The predicate with the setup expressions. - The type of the mocked object to query. - - - - Creates an mock object of the indicated type. - - The type of the mocked object. - The mocked object created. - - - - Creates an mock object of the indicated type. - - The predicate with the setup expressions. - The type of the mocked object. - The mocked object created. - - - - Creates the mock query with the underlying queriable implementation. - - - - - Wraps the enumerator inside a queryable. - - - - - Method that is turned into the actual call from .Query{T}, to - transform the queryable query into a normal enumerable query. - This method is never used directly by consumers. - - - - - Extension method used to support Linq-like setup properties that are not virtual but do have - a getter and a setter, thereby allowing the use of Linq to Mocks to quickly initialize Dtos too :) - - - - - Helper extensions that are used by the query translator. - - - - - Retrieves a fluent mock from the given setup expression. - - - - - Gets an autogenerated interface with a method on it that matches the signature of the specified - . - - - Such an interface can then be mocked, and a delegate pointed at the method on the mocked instance. - This is how we support delegate mocking. The factory caches such interfaces and reuses them - for repeated requests for the same delegate type. - - The delegate type for which an interface is required. - The method on the autogenerated interface. - - - - - - - - - - Defines the number of invocations allowed by a mocked method. - - - - - Specifies that a mocked method should be invoked times as minimum. - The minimun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as minimum. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked time as maximun. - The maximun number of times.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked one time as maximun. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked between and - times. - The minimun number of times.The maximun number of times. - The kind of range. See . - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly times. - The times that a method or property can be called.An object defining the allowed number of invocations. - - - - Specifies that a mocked method should not be invoked. - An object defining the allowed number of invocations. - - - - Specifies that a mocked method should be invoked exactly one time. - An object defining the allowed number of invocations. - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Determines whether two specified objects have the same value. - - The first . - - The second . - - true if the value of left is the same as the value of right; otherwise, false. - - - - - Determines whether two specified objects have different values. - - The first . - - The second . - - true if the value of left is different from the value of right; otherwise, false. - - - - diff --git a/Lib/NativeBinaries/amd64/git2-e0383fa.dll b/Lib/NativeBinaries/amd64/git2-e0383fa.dll deleted file mode 100644 index bd7a365be..000000000 Binary files a/Lib/NativeBinaries/amd64/git2-e0383fa.dll and /dev/null differ diff --git a/Lib/NativeBinaries/amd64/git2-e0383fa.pdb b/Lib/NativeBinaries/amd64/git2-e0383fa.pdb deleted file mode 100644 index c0e785800..000000000 Binary files a/Lib/NativeBinaries/amd64/git2-e0383fa.pdb and /dev/null differ diff --git a/Lib/NativeBinaries/libgit2.license.txt b/Lib/NativeBinaries/libgit2.license.txt deleted file mode 100644 index d1ca4d401..000000000 --- a/Lib/NativeBinaries/libgit2.license.txt +++ /dev/null @@ -1,930 +0,0 @@ - libgit2 is Copyright (C) the libgit2 contributors, - unless otherwise stated. See the AUTHORS file for details. - - Note that the only valid version of the GPL as far as this project - is concerned is _this_ particular version of the license (ie v2, not - v2.2 or v3.x or whatever), unless explicitly otherwise stated. - ----------------------------------------------------------------------- - - LINKING EXCEPTION - - In addition to the permissions in the GNU General Public License, - the authors give you unlimited permission to link the compiled - version of this library into combinations with other programs, - and to distribute those combinations without any restriction - coming from the use of this file. (The General Public License - restrictions do apply in other respects; for example, they cover - modification of the file, and distribution when not linked into - a combined executable.) - ----------------------------------------------------------------------- - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -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 - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly Mark Adler - jloup@gzip.org madler@alumni.caltech.edu - ----------------------------------------------------------------------- - -The priority queue implementation is based on code licensed under the -Apache 2.0 license: - - Copyright 2010 Volkan Yazıcı - Copyright 2006-2010 The Apache Software Foundation - -The full text of the Apache 2.0 license is available at: - - http://www.apache.org/licenses/LICENSE-2.0 - ----------------------------------------------------------------------- - -The Clay framework is licensed under the MIT license: - -Copyright (C) 2011 by Vicent Marti - -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. - ----------------------------------------------------------------------- - -The regex library (deps/regex/) is licensed under the GNU LGPL - - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Ty Coon, President of Vice - -That's all there is to it! diff --git a/Lib/NativeBinaries/x86/git2-e0383fa.dll b/Lib/NativeBinaries/x86/git2-e0383fa.dll deleted file mode 100644 index 6cb75dc4b..000000000 Binary files a/Lib/NativeBinaries/x86/git2-e0383fa.dll and /dev/null differ diff --git a/Lib/NativeBinaries/x86/git2-e0383fa.pdb b/Lib/NativeBinaries/x86/git2-e0383fa.pdb deleted file mode 100644 index b26876b55..000000000 Binary files a/Lib/NativeBinaries/x86/git2-e0383fa.pdb and /dev/null differ diff --git a/Lib/NuGet/NuGet.exe b/Lib/NuGet/NuGet.exe deleted file mode 100644 index cb3ed0367..000000000 Binary files a/Lib/NuGet/NuGet.exe and /dev/null differ diff --git a/Lib/NuGet/NuGet.license.txt b/Lib/NuGet/NuGet.license.txt deleted file mode 100644 index 48715cacc..000000000 --- a/Lib/NuGet/NuGet.license.txt +++ /dev/null @@ -1,29 +0,0 @@ -This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. - -1. Definitions - -The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. - -A "contribution" is the original software, or any additions or changes to the software. - -A "contributor" is any person that distributes its contribution under this license. - -"Licensed patents" are a contributor's patent claims that read directly on its contribution. - -2. Grant of Rights - -(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - -(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - -3. Conditions and Limitations - -(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - -(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. - -(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. - -(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. - -(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. diff --git a/Lib/xUnit/xUnit.license.txt b/Lib/xUnit/xUnit.license.txt deleted file mode 100644 index 48715cacc..000000000 --- a/Lib/xUnit/xUnit.license.txt +++ /dev/null @@ -1,29 +0,0 @@ -This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. - -1. Definitions - -The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. - -A "contribution" is the original software, or any additions or changes to the software. - -A "contributor" is any person that distributes its contribution under this license. - -"Licensed patents" are a contributor's patent claims that read directly on its contribution. - -2. Grant of Rights - -(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. - -(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. - -3. Conditions and Limitations - -(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. - -(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. - -(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. - -(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. - -(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. diff --git a/Lib/xUnit/xunit.dll b/Lib/xUnit/xunit.dll deleted file mode 100644 index 86168af53..000000000 Binary files a/Lib/xUnit/xunit.dll and /dev/null differ diff --git a/Lib/xUnit/xunit.dll.tdnet b/Lib/xUnit/xunit.dll.tdnet deleted file mode 100644 index d14ca810f..000000000 --- a/Lib/xUnit/xunit.dll.tdnet +++ /dev/null @@ -1,5 +0,0 @@ - - xUnit.net {0}.{1}.{2} build {3} - xunit.runner.tdnet.dll - Xunit.Runner.TdNet.TdNetRunner - \ No newline at end of file diff --git a/Lib/xUnit/xunit.extensions.dll b/Lib/xUnit/xunit.extensions.dll deleted file mode 100644 index 0b1907e63..000000000 Binary files a/Lib/xUnit/xunit.extensions.dll and /dev/null differ diff --git a/Lib/xUnit/xunit.extensions.xml b/Lib/xUnit/xunit.extensions.xml deleted file mode 100644 index 7434835a5..000000000 --- a/Lib/xUnit/xunit.extensions.xml +++ /dev/null @@ -1,805 +0,0 @@ - - - - xunit.extensions - - - - - A wrapper for Assert which is used by . - - - - - Verifies that a collection contains a given object. - - The type of the object to be verified - The object expected to be in the collection - The collection to be inspected - Thrown when the object is not present in the collection - - - - Verifies that a collection contains a given object, using an equality comparer. - - The type of the object to be verified - The object expected to be in the collection - The collection to be inspected - The comparer used to equate objects in the collection with the expected object - Thrown when the object is not present in the collection - - - - Verifies that a string contains a given sub-string, using the current culture. - - The sub-string expected to be in the string - The string to be inspected - Thrown when the sub-string is not present inside the string - - - - Verifies that a string contains a given sub-string, using the given comparison type. - - The sub-string expected to be in the string - The string to be inspected - The type of string comparison to perform - Thrown when the sub-string is not present inside the string - - - - Verifies that a collection does not contain a given object. - - The type of the object to be compared - The object that is expected not to be in the collection - The collection to be inspected - Thrown when the object is present inside the container - - - - Verifies that a collection does not contain a given object, using an equality comparer. - - The type of the object to be compared - The object that is expected not to be in the collection - The collection to be inspected - The comparer used to equate objects in the collection with the expected object - Thrown when the object is present inside the container - - - - Verifies that a string does not contain a given sub-string, using the current culture. - - The sub-string which is expected not to be in the string - The string to be inspected - Thrown when the sub-string is present inside the string - - - - Verifies that a string does not contain a given sub-string, using the current culture. - - The sub-string which is expected not to be in the string - The string to be inspected - The type of string comparison to perform - Thrown when the sub-string is present inside the given string - - - - Verifies that a block of code does not throw any exceptions. - - A delegate to the code to be tested - - - - Verifies that a collection is empty. - - The collection to be inspected - Thrown when the collection is null - Thrown when the collection is not empty - - - - Verifies that two objects are equal, using a default comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - Thrown when the objects are not equal - - - - Verifies that two objects are equal, using a custom equatable comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - The comparer used to compare the two objects - Thrown when the objects are not equal - - - - Verifies that two values are equal, within the number of decimal - places given by . - - The expected value - The value to be compared against - The number of decimal places (valid values: 0-15) - Thrown when the values are not equal - - - - Verifies that two values are equal, within the number of decimal - places given by . - - The expected value - The value to be compared against - The number of decimal places (valid values: 0-15) - Thrown when the values are not equal - - - - Verifies that the condition is false. - - The condition to be tested - Thrown if the condition is not false - - - - Verifies that the condition is false. - - The condition to be tested - The message to show when the condition is not false - Thrown if the condition is not false - - - - Verifies that a value is within a given range. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - Thrown when the value is not in the given range - - - - Verifies that a value is within a given range, using a comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - The comparer used to evaluate the value's range - Thrown when the value is not in the given range - - - - Verifies that an object is of the given type or a derived type. - - The type the object should be - The object to be evaluated - The object, casted to type T when successful - Thrown when the object is not the given type - - - - Verifies that an object is of the given type or a derived type. - - The type the object should be - The object to be evaluated - Thrown when the object is not the given type - - - - Verifies that an object is not exactly the given type. - - The type the object should not be - The object to be evaluated - Thrown when the object is the given type - - - - Verifies that an object is not exactly the given type. - - The type the object should not be - The object to be evaluated - Thrown when the object is the given type - - - - Verifies that an object is exactly the given type (and not a derived type). - - The type the object should be - The object to be evaluated - The object, casted to type T when successful - Thrown when the object is not the given type - - - - Verifies that an object is exactly the given type (and not a derived type). - - The type the object should be - The object to be evaluated - Thrown when the object is not the given type - - - - Verifies that a collection is not empty. - - The collection to be inspected - Thrown when a null collection is passed - Thrown when the collection is empty - - - - Verifies that two objects are not equal, using a default comparer. - - The type of the objects to be compared - The expected object - The actual object - Thrown when the objects are equal - - - - Verifies that two objects are not equal, using a custom equality comparer. - - The type of the objects to be compared - The expected object - The actual object - The comparer used to examine the objects - Thrown when the objects are equal - - - - Verifies that a value is not within a given range, using the default comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - Thrown when the value is in the given range - - - - Verifies that a value is not within a given range, using a comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - The comparer used to evaluate the value's range - Thrown when the value is in the given range - - - - Verifies that an object reference is not null. - - The object to be validated - Thrown when the object is not null - - - - Verifies that two objects are not the same instance. - - The expected object instance - The actual object instance - Thrown when the objects are the same instance - - - - Verifies that an object reference is null. - - The object to be inspected - Thrown when the object reference is not null - - - - Verifies that two objects are the same instance. - - The expected object instance - The actual object instance - Thrown when the objects are not the same instance - - - - Verifies that the given collection contains only a single - element of the given type. - - The collection. - The single item in the collection. - Thrown when the collection does not contain - exactly one element. - - - - Verifies that the given collection contains only a single - element of the given type. - - The collection type. - The collection. - The single item in the collection. - Thrown when the collection does not contain - exactly one element. - - - - Verifies that the exact exception is thrown (and not a derived exception type). - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - Generally used to test property accessors. - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - Generally used to test property accessors. - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that an expression is true. - - The condition to be inspected - Thrown when the condition is false - - - - Verifies that an expression is true. - - The condition to be inspected - The message to be shown when the condition is false - Thrown when the condition is false - - - - A class which can be derived from for test classes, which bring an overridable version - of Assert (using the class. - - - - - Gets a class which provides assertions. - - - - - Apply this attribute to your test method to replace the - with another role. - - - - - Replaces the identity of the current thread with . - - The role's name - - - - Restores the original . - - The method under test - - - - Stores the current and replaces it with - a new role identified in constructor. - - The method under test - - - - Gets the name. - - - - - Apply this attribute to your test method to automatically create a - that is rolled back when the test is - finished. - - - - - Rolls back the transaction. - - - - - Creates the transaction. - - - - - Gets or sets the isolation level of the transaction. - Default value is .Unspecified. - - - - - Gets or sets the scope option for the transaction. - Default value is .Required. - - - - - Gets or sets the timeout of the transaction, in milliseconds. - By default, the transaction will not timeout. - - - - - Provides a data source for a data theory, with the data coming from a class - which must implement IEnumerable<object[]>. - - - - - Abstract attribute which represents a data source for a data theory. - Data source providers derive from this attribute and implement GetData - to return the data for the theory. - - - - - Returns the data to be used to test the theory. - - - The parameter is provided so that the - test data can be converted to the destination parameter type when necessary. - Generally, data should NOT be automatically converted, UNLESS the source data - format does not have rich types (for example, all numbers in Excel spreadsheets - are returned as even if they are integers). Derivers of - this class should NOT throw exceptions for mismatched types or mismatched number - of parameters; the test framework will throw these exceptions at the correct - time. - - The method that is being tested - The types of the parameters for the test method - The theory data - - - - - - - Initializes a new instance of the class. - - The class that provides the data. - - - - - - - Gets the type of the class that provides the data. - - - - - Represents an implementation of which uses an - instance of to get the data for a - decorated test method. - - - - - - - - Converts a parameter to its destination parameter type, if necessary. - - The parameter value - The destination parameter type (null if not known) - The converted parameter value - - - - Gets the data adapter to be used to retrieve the test data. - - - - - Provides a data source for a data theory, with the data coming from inline values. - - - - - Initializes a new instance of the class. - - The data values to pass to the theory - - - - Returns the data to be used to test the theory. - - The method that is being tested - The types of the parameters for the test method - The theory data, in table form - - - - Gets the data values. - - - - - Provides a data source for a data theory, with the data coming from an OLEDB connection. - - - - - Creates a new instance of . - - The OLEDB connection string to the data - The SELECT statement used to return the data for the theory - - - - Gets the connection string. - - - - - Gets the select statement. - - - - - - - - Provides a data source for a data theory, with the data coming from a public static property on the test class. - The property must return IEnumerable<object[]> with the test data. - - - - - Creates a new instance of / - - The name of the public static property on the test class that will provide the test data - - - - Returns the data to be used to test the theory. - - The method that is being tested - The types of the parameters for the test method - The theory data, in table form - - - - Gets the property name. - - - - - Provides a data source for a data theory, with the data coming a Microsoft SQL Server. - - - - - Creates a new instance of , using a trusted connection. - - The server name of the Microsoft SQL Server - The database name - The SQL SELECT statement to return the data for the data theory - - - - Creates a new instance of , using the provided username and password. - - The server name of the Microsoft SQL Server - The database name - The username for the server - The password for the server - The SQL SELECT statement to return the data for the data theory - - - - Provides a data source for a data theory, with the data coming a Microsoft Excel (.xls) spreadsheet. - - - - - Creates a new instance of . - - The filename of the XLS spreadsheet file; if the filename provided - is relative, then it is relative to the location of xunit.extensions.dll. - The SELECT statement that returns the data for the theory - - - - - - - A wrapper around the static operations on which allows time - to be frozen using the . The clock begins in the - thawed state; that is, calls to , , and - return current (non-frozen) values. - - - - - Freezes the clock with the current time. - Until is called, all calls to , , and - will return the exact same values. - - - - - Freezes the clock with the given date and time, considered to be local time. - Until is called, all calls to , , and - will return the exact same values. - - The local date and time to freeze to - - - - Freezes the clock with the given date and time, considered to be Coordinated Universal Time (UTC). - Until is called, all calls to , , and - will return the exact same values. - - The UTC date and time to freeze to - - - - Thaws the clock so that , , and - return normal values. - - - - - Gets a object that is set to the current date and time on this computer, - expressed as the local time. - - - - - Gets the current date. - - - - - Gets a object that is set to the current date and time on this computer, - expressed as the Coordinated Universal Time (UTC). - - - - - Apply this attribute to your test method to freeze the time represented by the - class. - - - - - Freeze the clock with the current date and time. - - - - - Freeze the clock with the given date, considered to be local time. - - The frozen year - The frozen month - The frozen day - - - - Freeze the clock with the given date and time, considered to be in local time. - - The frozen year - The frozen month - The frozen day - The frozen hour - The frozen minute - The frozen second - - - - Freeze the clock with the given date and time, with the given kind of time. - - The frozen year - The frozen month - The frozen day - The frozen hour - The frozen minute - The frozen second - The frozen time kind - - - - Thaws the clock. - - The method under test - - - - Freezes the clock. - - The method under test - - - - Marks a test method as being a data theory. Data theories are tests which are fed - various bits of data from a data source, mapping to parameters on the test method. - If the data source contains multiple rows, then the test method is executed - multiple times (once with each data row). - - - - - Creates instances of which represent individual intended - invocations of the test method, one per data row in the data source. - - The method under test - An enumerator through the desired test method invocations - - - - Represents a single invocation of a data theory test method. - - - - - Creates a new instance of . - - The method under test - The parameters to be passed to the test method - - - - Creates a new instance of based on a generic theory. - - The method under test - The parameters to be passed to the test method - The generic types that were used to resolved the generic method. - - - - - - - Gets the parameter values that are passed to the test method. - - - - - Apply to a test method to trace the method begin and end. - - - - - This method is called before the test method is executed. - - The method under test - - - - This method is called after the test method is executed. - - The method under test - - - diff --git a/Lib/xUnit/xunit.runner.msbuild.dll b/Lib/xUnit/xunit.runner.msbuild.dll deleted file mode 100644 index ac2065d02..000000000 Binary files a/Lib/xUnit/xunit.runner.msbuild.dll and /dev/null differ diff --git a/Lib/xUnit/xunit.runner.tdnet.dll b/Lib/xUnit/xunit.runner.tdnet.dll deleted file mode 100644 index f7d0639ae..000000000 Binary files a/Lib/xUnit/xunit.runner.tdnet.dll and /dev/null differ diff --git a/Lib/xUnit/xunit.runner.utility.dll b/Lib/xUnit/xunit.runner.utility.dll deleted file mode 100644 index cc1f21bf8..000000000 Binary files a/Lib/xUnit/xunit.runner.utility.dll and /dev/null differ diff --git a/Lib/xUnit/xunit.xml b/Lib/xUnit/xunit.xml deleted file mode 100644 index 4b60b4400..000000000 --- a/Lib/xUnit/xunit.xml +++ /dev/null @@ -1,2604 +0,0 @@ - - - - xunit - - - - - Contains various static methods that are used to verify that conditions are met during the - process of running tests. - - - - - Initializes a new instance of the class. - - - - - Verifies that a collection contains a given object. - - The type of the object to be verified - The object expected to be in the collection - The collection to be inspected - Thrown when the object is not present in the collection - - - - Verifies that a collection contains a given object, using an equality comparer. - - The type of the object to be verified - The object expected to be in the collection - The collection to be inspected - The comparer used to equate objects in the collection with the expected object - Thrown when the object is not present in the collection - - - - Verifies that a string contains a given sub-string, using the current culture. - - The sub-string expected to be in the string - The string to be inspected - Thrown when the sub-string is not present inside the string - - - - Verifies that a string contains a given sub-string, using the given comparison type. - - The sub-string expected to be in the string - The string to be inspected - The type of string comparison to perform - Thrown when the sub-string is not present inside the string - - - - Verifies that a collection does not contain a given object. - - The type of the object to be compared - The object that is expected not to be in the collection - The collection to be inspected - Thrown when the object is present inside the container - - - - Verifies that a collection does not contain a given object, using an equality comparer. - - The type of the object to be compared - The object that is expected not to be in the collection - The collection to be inspected - The comparer used to equate objects in the collection with the expected object - Thrown when the object is present inside the container - - - - Verifies that a string does not contain a given sub-string, using the current culture. - - The sub-string which is expected not to be in the string - The string to be inspected - Thrown when the sub-string is present inside the string - - - - Verifies that a string does not contain a given sub-string, using the current culture. - - The sub-string which is expected not to be in the string - The string to be inspected - The type of string comparison to perform - Thrown when the sub-string is present inside the given string - - - - Verifies that a block of code does not throw any exceptions. - - A delegate to the code to be tested - - - - Verifies that a collection is empty. - - The collection to be inspected - Thrown when the collection is null - Thrown when the collection is not empty - - - - Verifies that two objects are equal, using a default comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - Thrown when the objects are not equal - - - - Verifies that two objects are equal, using a custom equatable comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - The comparer used to compare the two objects - Thrown when the objects are not equal - - - - Verifies that two values are equal, within the number of decimal - places given by . - - The expected value - The value to be compared against - The number of decimal places (valid values: 0-15) - Thrown when the values are not equal - - - - Verifies that two values are equal, within the number of decimal - places given by . - - The expected value - The value to be compared against - The number of decimal places (valid values: 0-15) - Thrown when the values are not equal - - - - Verifies that two sequences are equivalent, using a default comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - Thrown when the objects are not equal - - - - Verifies that two sequences are equivalent, using a custom equatable comparer. - - The type of the objects to be compared - The expected value - The value to be compared against - The comparer used to compare the two objects - Thrown when the objects are not equal - - - Do not call this method. - - - - Verifies that the condition is false. - - The condition to be tested - Thrown if the condition is not false - - - - Verifies that the condition is false. - - The condition to be tested - The message to show when the condition is not false - Thrown if the condition is not false - - - - Verifies that a value is within a given range. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - Thrown when the value is not in the given range - - - - Verifies that a value is within a given range, using a comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - The comparer used to evaluate the value's range - Thrown when the value is not in the given range - - - - Verifies that an object is of the given type or a derived type. - - The type the object should be - The object to be evaluated - The object, casted to type T when successful - Thrown when the object is not the given type - - - - Verifies that an object is of the given type or a derived type. - - The type the object should be - The object to be evaluated - Thrown when the object is not the given type - - - - Verifies that an object is not exactly the given type. - - The type the object should not be - The object to be evaluated - Thrown when the object is the given type - - - - Verifies that an object is not exactly the given type. - - The type the object should not be - The object to be evaluated - Thrown when the object is the given type - - - - Verifies that an object is exactly the given type (and not a derived type). - - The type the object should be - The object to be evaluated - The object, casted to type T when successful - Thrown when the object is not the given type - - - - Verifies that an object is exactly the given type (and not a derived type). - - The type the object should be - The object to be evaluated - Thrown when the object is not the given type - - - - Verifies that a collection is not empty. - - The collection to be inspected - Thrown when a null collection is passed - Thrown when the collection is empty - - - - Verifies that two objects are not equal, using a default comparer. - - The type of the objects to be compared - The expected object - The actual object - Thrown when the objects are equal - - - - Verifies that two objects are not equal, using a custom equality comparer. - - The type of the objects to be compared - The expected object - The actual object - The comparer used to examine the objects - Thrown when the objects are equal - - - - Verifies that two sequences are not equivalent, using a default comparer. - - The type of the objects to be compared - The expected object - The actual object - Thrown when the objects are equal - - - - Verifies that two sequences are not equivalent, using a custom equality comparer. - - The type of the objects to be compared - The expected object - The actual object - The comparer used to compare the two objects - Thrown when the objects are equal - - - - Verifies that a value is not within a given range, using the default comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - Thrown when the value is in the given range - - - - Verifies that a value is not within a given range, using a comparer. - - The type of the value to be compared - The actual value to be evaluated - The (inclusive) low value of the range - The (inclusive) high value of the range - The comparer used to evaluate the value's range - Thrown when the value is in the given range - - - - Verifies that an object reference is not null. - - The object to be validated - Thrown when the object is not null - - - - Verifies that two objects are not the same instance. - - The expected object instance - The actual object instance - Thrown when the objects are the same instance - - - - Verifies that an object reference is null. - - The object to be inspected - Thrown when the object reference is not null - - - - Verifies that the provided object raised INotifyPropertyChanged.PropertyChanged - as a result of executing the given test code. - - The object which should raise the notification - The property name for which the notification should be raised - The test code which should cause the notification to be raised - Thrown when the notification is not raised - - - - Verifies that two objects are the same instance. - - The expected object instance - The actual object instance - Thrown when the objects are not the same instance - - - - Verifies that the given collection contains only a single - element of the given type. - - The collection. - The single item in the collection. - Thrown when the collection does not contain - exactly one element. - - - - Verifies that the given collection contains only a single - element of the given value. The collection may or may not - contain other values. - - The collection. - The value to find in the collection. - The single item in the collection. - Thrown when the collection does not contain - exactly one element. - - - - Verifies that the given collection contains only a single - element of the given type. - - The collection type. - The collection. - The single item in the collection. - Thrown when the collection does not contain - exactly one element. - - - - Verifies that the given collection contains only a single - element of the given type which matches the given predicate. The - collection may or may not contain other values which do not - match the given predicate. - - The collection type. - The collection. - The item matching predicate. - The single item in the filtered collection. - Thrown when the filtered collection does - not contain exactly one element. - - - - Verifies that the exact exception is thrown (and not a derived exception type). - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - Generally used to test property accessors. - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that the exact exception is thrown (and not a derived exception type). - Generally used to test property accessors. - - The type of the exception expected to be thrown - A delegate to the code to be tested - The exception that was thrown, when successful - Thrown when an exception was not thrown, or when an exception of the incorrect type is thrown - - - - Verifies that an expression is true. - - The condition to be inspected - Thrown when the condition is false - - - - Verifies that an expression is true. - - The condition to be inspected - The message to be shown when the condition is false - Thrown when the condition is false - - - - Used by the PropertyChanged. - - - - - Used by the Throws and DoesNotThrow methods. - - - - - Used by the Throws and DoesNotThrow methods. - - - - - This command sets up the necessary trace listeners and standard - output/error listeners to capture Assert/Debug.Trace failures, - output to stdout/stderr, and Assert/Debug.Write text. It also - captures any exceptions that are thrown and packages them as - FailedResults, including the possibility that the configuration - file is messed up (which is exposed when we attempt to manipulate - the trace listener list). - - - - - Base class used by commands which delegate to inner commands. - - - - - Interface which represents the ability to invoke of a test method. - - - - - Executes the test method. - - The instance of the test class - Returns information about the test run - - - - Creates the start XML to be sent to the callback when the test is about to start - running. - - Return the of the start node, or null if the test - is known that it will not be running. - - - - Gets the display name of the test method. - - - - - Determines if the test runner infrastructure should create a new instance of the - test class before running the test. - - - - - Determines if the test should be limited to running a specific amount of time - before automatically failing. - - The timeout value, in milliseconds; if zero, the test will not have - a timeout. - - - - Creates a new instance of the class. - - The inner command to delegate to. - - - - - - - - - - - - - - - - - - - - - - Initializes a new instance of the - class. - - The command that will be wrapped. - The test method. - - - - - - - Represents an implementation of to be used with - tests which are decorated with the . - - - - - Represents an xUnit.net test command. - - - - - The method under test. - - - - - Initializes a new instance of the class. - - The method under test. - The display name of the test. - The timeout, in milliseconds. - - - - - - - - - - - - - Gets the name of the method under test. - - - - - - - - - - - Gets the name of the type under test. - - - - - Initializes a new instance of the class. - - The test method. - - - - - - - Base class for exceptions that have actual and expected values - - - - - The base assert exception class - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - The user message to be displayed - - - - Initializes a new instance of the class. - - The user message to be displayed - The inner exception - - - - Initializes a new instance of the class. - - The user message to be displayed - The stack trace to be displayed - - - - - - - Determines whether to exclude a line from the stack frame. By default, this method - removes all stack frames from methods beginning with Xunit.Assert or Xunit.Sdk. - - The stack frame to be filtered. - Return true to exclude the line from the stack frame; false, otherwise. - - - - Filters the stack trace to remove all lines that occur within the testing framework. - - The original stack trace - The filtered stack trace - - - - - - - Gets a string representation of the frames on the call stack at the time the current exception was thrown. - - A string that describes the contents of the call stack, with the most recent method call appearing first. - - - - Gets the user message - - - - - Creates a new instance of the class. - - The expected value - The actual value - The user message to be shown - - - - Creates a new instance of the class. - - The expected value - The actual value - The user message to be shown - Set to true to skip the check for difference position - - - - - - - - - - Gets the actual value. - - - - - Gets the expected value. - - - - - Gets a message that describes the current exception. Includes the expected and actual values. - - The error message that explains the reason for the exception, or an empty string(""). - 1 - - - - Exception thrown when a collection unexpectedly does not contain the expected value. - - - - - Creates a new instance of the class. - - The expected object value - - - - Creates a new instance of the class. - - The expected object value - The actual value - - - - - - - Exception to be thrown from when the number of - parameter values does not the test method signature. - - - - - - - - - - - Exception thrown when code unexpectedly fails change a property. - - - - - Creates a new instance of the class. Call this constructor - when no exception was thrown. - - The name of the property that was expected. - - - - - - - Exception thrown when the collection did not contain exactly one element. - - - - - Initializes a new instance of the class. - - The numbers of items in the collection. - - - - Initializes a new instance of the class. - - The numbers of items in the collection. - The object expected to be in the collection. - - - - - - - Internal class used for version-resilient test runners. DO NOT CALL DIRECTLY. - Version-resilient runners should link against xunit.runner.utility.dll and use - ExecutorWrapper instead. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Exception thrown when the value is unexpectedly not of the given type or a derived type. - - - - - Creates a new instance of the class. - - The expected type - The actual object value - - - - - - - Allows the user to record actions for a test. - - - - - Records any exception which is thrown by the given code. - - The code which may thrown an exception. - Returns the exception that was thrown by the code; null, otherwise. - - - - Records any exception which is thrown by the given code that has - a return value. Generally used for testing property accessors. - - The code which may thrown an exception. - Returns the exception that was thrown by the code; null, otherwise. - - - - Exception that is thrown when one or more exceptions are thrown from - the After method of a . - - - - - Initializes a new instance of the class. - - The exceptions. - - - - Initializes a new instance of the class. - - The exceptions. - - - - - - - - - - Gets the list of exceptions thrown in the After method. - - - - - Gets a message that describes the current exception. - - - - - Gets a string representation of the frames on the call stack at the time the current exception was thrown. - - - - - Implementation of which executes the - instances attached to a test method. - - - - - Initializes a new instance of the class. - - The inner command. - The method. - - - - Executes the test method. - - The instance of the test class - Returns information about the test run - - - - This class supports the xUnit.net infrastructure and is not intended to be used - directly from your code. - - - - - This API supports the xUnit.net infrastructure and is not intended to be used - directly from your code. - - - - - This API supports the xUnit.net infrastructure and is not intended to be used - directly from your code. - - - - - This API supports the xUnit.net infrastructure and is not intended to be used - directly from your code. - - - - - Guard class, used for guard clauses and argument validation - - - - - - - - - - - - - - Base class which contains XML manipulation helper methods - - - - - Interface that represents a single test result. - - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - The amount of time spent in execution - - - - - Adds the test execution time to the XML node. - - The XML node. - - - - - - - - - - Utility methods for dealing with exceptions. - - - - - Gets the message for the exception, including any inner exception messages. - - The exception - The formatted message - - - - Gets the stack trace for the exception, including any inner exceptions. - - The exception - The formatted stack trace - - - - Rethrows an exception object without losing the existing stack trace information - - The exception to re-throw. - - For more information on this technique, see - http://www.dotnetjunkies.com/WebLog/chris.taylor/archive/2004/03/03/8353.aspx - - - - - A dictionary which contains multiple unique values for each key. - - The type of the key. - The type of the value. - - - - Adds the value for the given key. If the key does not exist in the - dictionary yet, it will add it. - - The key. - The value. - - - - Removes all keys and values from the dictionary. - - - - - Determines whether the dictionary contains to specified key and value. - - The key. - The value. - - - - Calls the delegate once for each key/value pair in the dictionary. - - - - - Removes the given key and all of its values. - - - - - Removes the given value from the given key. If this was the - last value for the key, then the key is removed as well. - - The key. - The value. - - - - Gets the values for the given key. - - - - - Gets the count of the keys in the dictionary. - - - - - Gets the keys. - - - - - - - - XML utility methods - - - - - Adds an attribute to an XML node. - - The XML node. - The attribute name. - The attribute value. - - - - Adds a child element to an XML node. - - The parent XML node. - The child element name. - The new child XML element. - - - - Exception that is thrown when a call to Debug.Assert() fails. - - - - - Creates a new instance of the class. - - The original assert message - - - - Creates a new instance of the class. - - The original assert message - The original assert detailed message - - - - - - - - - - Gets the original assert detailed message. - - - - - Gets the original assert message. - - - - - Gets a message that describes the current exception. - - - - - Exception thrown when a collection unexpectedly contains the expected value. - - - - - Creates a new instance of the class. - - The expected object value - - - - - - - Exception thrown when code unexpectedly throws an exception. - - - - - Creates a new instance of the class. - - Actual exception - - - - THIS CONSTRUCTOR IS FOR UNIT TESTING PURPOSES ONLY. - - - - - - - - - - - Gets a string representation of the frames on the call stack at the time the current exception was thrown. - - A string that describes the contents of the call stack, with the most recent method call appearing first. - - - - Exception thrown when a collection is unexpectedly not empty. - - - - - Creates a new instance of the class. - - - - - - - - Exception thrown when two values are unexpectedly not equal. - - - - - Creates a new instance of the class. - - The expected object value - The actual object value - - - - Creates a new instance of the class. - - The expected object value - The actual object value - Set to true to skip the check for difference position - - - - - - - Exception thrown when a value is unexpectedly true. - - - - - Creates a new instance of the class. - - The user message to be display, or null for the default message - - - - - - - Exception thrown when a value is unexpectedly not in the given range. - - - - - Creates a new instance of the class. - - The actual object value - The low value of the range - The high value of the range - - - - - - - - - - Gets the actual object value - - - - - Gets the high value of the range - - - - - Gets the low value of the range - - - - - Gets a message that describes the current exception. - - The error message that explains the reason for the exception, or an empty string(""). - - - - Exception thrown when the value is unexpectedly of the exact given type. - - - - - Creates a new instance of the class. - - The expected type - The actual object value - - - - - - - Exception thrown when the value is unexpectedly not of the exact given type. - - - - - Creates a new instance of the class. - - The expected type - The actual object value - - - - - - - Used to decorate xUnit.net test classes that utilize fixture classes. - An instance of the fixture data is initialized just before the first - test in the class is run, and if it implements IDisposable, is disposed - after the last test in the class is run. - - The type of the fixture - - - - Called on the test class just before each test method is run, - passing the fixture data so that it can be used for the test. - All test runs share the same instance of fixture data. - - The fixture data - - - - Exception thrown when a value is unexpectedly in the given range. - - - - - Creates a new instance of the class. - - The actual object value - The low value of the range - The high value of the range - - - - - - - - - - Gets the actual object value - - - - - Gets the high value of the range - - - - - Gets the low value of the range - - - - - Gets a message that describes the current exception. - - The error message that explains the reason for the exception, or an empty string(""). - - - - Base attribute which indicates a test method interception (allows code to be run before and - after the test is run). - - - - - This method is called after the test method is executed. - - The method under test - - - - This method is called before the test method is executed. - - The method under test - - - - - - - Exception thrown when a collection is unexpectedly empty. - - - - - Creates a new instance of the class. - - - - - - - - Exception thrown when two values are unexpectedly equal. - - - - - Creates a new instance of the class. - - - - - - - - Exception thrown when an object is unexpectedly null. - - - - - Creates a new instance of the class. - - - - - - - - Exception thrown when two values are unexpected the same instance. - - - - - Creates a new instance of the class. - - - - - - - - Exception thrown when an object reference is unexpectedly not null. - - - - - Creates a new instance of the class. - - - - - - - - - Command that automatically creates the instance of the test class - and disposes it (if it implements ). - - - - - Creates a new instance of the object. - - The command that is bring wrapped - The method under test - - - - Executes the test method. Creates a new instance of the class - under tests and passes it to the inner command. Also catches - any exceptions and converts them into s. - - The instance of the test class - Returns information about the test run - - - - Command used to wrap a which has associated - fixture data. - - - - - Creates a new instance of the class. - - The inner command - The fixtures to be set on the test class - - - - Sets the fixtures on the test class by calling SetFixture, then - calls the inner command. - - The instance of the test class - Returns information about the test run - - - - A timer class used to figure out how long tests take to run. On most .NET implementations - this will use the class because it's a high - resolution timer; however, on Silverlight/CoreCLR, it will use - (which will provide lower resolution results). - - - - - Creates a new instance of the class. - - - - - Starts timing. - - - - - Stops timing. - - - - - Gets how long the timer ran, in milliseconds. In order for this to be valid, - both and must have been called. - - - - - Attribute used to decorate a test method with arbitrary name/value pairs ("traits"). - - - - - Creates a new instance of the class. - - The trait name - The trait value - - - - Gets the trait name. - - - - - - - - Gets the trait value. - - - - - Runner that executes an synchronously. - - - - - Execute the . - - The test class command to execute - The methods to execute; if null or empty, all methods will be executed - The start run callback - The end run result callback - A with the results of the test run - - - - Factory for objects, based on the type under test. - - - - - Creates the test class command, which implements , for a given type. - - The type under test - The test class command, if the class is a test class; null, otherwise - - - - Creates the test class command, which implements , for a given type. - - The type under test - The test class command, if the class is a test class; null, otherwise - - - - Represents an xUnit.net test class - - - - - Interface which describes the ability to executes all the tests in a test class. - - - - - Allows the test class command to choose the next test to be run from the list of - tests that have not yet been run, thereby allowing it to choose the run order. - - The tests remaining to be run - The index of the test that should be run - - - - Execute actions to be run after all the test methods of this test class are run. - - Returns the thrown during execution, if any; null, otherwise - - - - Execute actions to be run before any of the test methods of this test class are run. - - Returns the thrown during execution, if any; null, otherwise - - - - Enumerates the test commands for a given test method in this test class. - - The method under test - The test commands for the given test method - - - - Enumerates the methods which are test methods in this test class. - - The test methods - - - - Determines if a given refers to a test method. - - The test method to validate - True if the method is a test method; false, otherwise - - - - Gets the object instance that is under test. May return null if you wish - the test framework to create a new object instance for each test method. - - - - - Gets or sets the type that is being tested - - - - - Creates a new instance of the class. - - - - - Creates a new instance of the class. - - The type under test - - - - Creates a new instance of the class. - - The type under test - - - - Chooses the next test to run, randomly, using the . - - The tests remaining to be run - The index of the test that should be run - - - - Execute actions to be run after all the test methods of this test class are run. - - Returns the thrown during execution, if any; null, otherwise - - - - Execute actions to be run before any of the test methods of this test class are run. - - Returns the thrown during execution, if any; null, otherwise - - - - Enumerates the test commands for a given test method in this test class. - - The method under test - The test commands for the given test method - - - - Enumerates the methods which are test methods in this test class. - - The test methods - - - - Determines if a given refers to a test method. - - The test method to validate - True if the method is a test method; false, otherwise - - - - Gets the object instance that is under test. May return null if you wish - the test framework to create a new object instance for each test method. - - - - - Gets or sets the randomizer used to determine the order in which tests are run. - - - - - Sets the type that is being tested - - - - - Implementation of that represents a skipped test. - - - - - Creates a new instance of the class. - - The method that is being skipped - The display name for the test. If null, the fully qualified - type name is used. - The reason the test was skipped. - - - - - - - - - - Gets the skip reason. - - - - - - - - Factory for creating objects. - - - - - Make instances of objects for the given class and method. - - The class command - The method under test - The set of objects - - - - A command wrapper which times the running of a command. - - - - - Creates a new instance of the class. - - The command that will be timed. - - - - Executes the inner test method, gathering the amount of time it takes to run. - - Returns information about the test run - - - - Wraps a command which should fail if it runs longer than the given timeout value. - - - - - Creates a new instance of the class. - - The command to be run - The timout, in milliseconds - The method under test - - - - Executes the test method, failing if it takes too long. - - Returns information about the test run - - - - - - - Attributes used to decorate a test fixture that is run with an alternate test runner. - The test runner must implement the interface. - - - - - Creates a new instance of the class. - - The class which implements ITestClassCommand and acts as the runner - for the test fixture. - - - - Gets the test class command. - - - - - Exception thrown when two object references are unexpectedly not the same instance. - - - - - Creates a new instance of the class. - - The expected object reference - The actual object reference - - - - - - - Contains the test results from an assembly. - - - - - Contains multiple test results, representing them as a composite test result. - - - - - Adds a test result to the composite test result list. - - - - - - Gets the test results. - - - - - Creates a new instance of the class. - - The filename of the assembly - - - - Creates a new instance of the class. - - The filename of the assembly - The configuration filename - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Gets the fully qualified filename of the configuration file. - - - - - Gets the directory where the assembly resides. - - - - - Gets the number of failed results. - - - - - Gets the fully qualified filename of the assembly. - - - - - Gets the number of passed results. - - - - - Gets the number of skipped results. - - - - - Contains the test results from a test class. - - - - - Creates a new instance of the class. - - The type under test - - - - Creates a new instance of the class. - - The simple name of the type under test - The fully qualified name of the type under test - The namespace of the type under test - - - - Sets the exception thrown by the test fixture. - - The thrown exception - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Gets the fully qualified test fixture exception type, when an exception has occurred. - - - - - Gets the number of tests which failed. - - - - - Gets the fully qualified name of the type under test. - - - - - Gets the test fixture exception message, when an exception has occurred. - - - - - Gets the simple name of the type under test. - - - - - Gets the namespace of the type under test. - - - - - Gets the number of tests which passed. - - - - - Gets the number of tests which were skipped. - - - - - Gets the test fixture exception stack trace, when an exception has occurred. - - - - - Represents a failed test result. - - - - - Represents the results from running a test method - - - - - Initializes a new instance of the class. The traits for - the test method are discovered using reflection. - - The method under test. - The display name for the test. If null, the fully qualified - type name is used. - - - - Initializes a new instance of the class. - - The name of the method under test. - The type of the method under test. - The display name for the test. If null, the fully qualified - type name is used. - The traits. - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Gets or sets the display name of the method under test. This is the value that's shown - during failures and in the resulting output XML. - - - - - Gets the name of the method under test. - - - - - Gets or sets the standard output/standard error from the test that was captured - while the test was running. - - - - - Gets the traits attached to the test method. - - - - - Gets the name of the type under test. - - - - - Creates a new instance of the class. - - The method under test - The exception throw by the test - The display name for the test. If null, the fully qualified - type name is used. - - - - Creates a new instance of the class. - - The name of the method under test - The name of the type under test - The display name of the test - The custom properties attached to the test method - The full type name of the exception throw - The exception message - The exception stack trace - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Gets the exception type thrown by the test method. - - - - - Gets the exception message thrown by the test method. - - - - - Gets the stack trace of the exception thrown by the test method. - - - - - Represents a passing test result. - - - - - Create a new instance of the class. - - The method under test - The display name for the test. If null, the fully qualified - type name is used. - - - - Create a new instance of the class. - - The name of the method under test - The name of the type under test - The display name for the test. If null, the fully qualified - type name is used. - The custom properties attached to the test method - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Represents a skipped test result. - - - - - Creates a new instance of the class. Uses reflection to discover - the skip reason. - - The method under test - The display name for the test. If null, the fully qualified - type name is used. - The reason the test was skipped. - - - - Creates a new instance of the class. - - The name of the method under test - The name of the type under test - The display name for the test. If null, the fully qualified - type name is used. - The traits attached to the method under test - The skip reason - - - - Converts the test result into XML that is consumed by the test runners. - - The parent node. - The newly created XML node. - - - - Gets the skip reason. - - - - - Represents information about an attribute. - - - - - Gets the instance of the attribute, if available. - - The type of the attribute - The instance of the attribute, if available. - - - - Gets an initialized property value of the attribute. - - The type of the property - The name of the property - The property value - - - - Represents information about a method. - - - - - Creates an instance of the type where this test method was found. If using - reflection, this should be the ReflectedType. - - A new instance of the type. - - - - Gets all the custom attributes for the method that are of the given type. - - The type of the attribute - The matching attributes that decorate the method - - - - Determines if the method has at least one instance of the given attribute type. - - The type of the attribute - True if the method has at least one instance of the given attribute type; false, otherwise - - - - Invokes the test on the given class, with the given parameters. - - The instance of the test class (may be null if - the test method is static). - The parameters to be passed to the test method. - - - - Gets a value which represents the class that this method was - reflected from (i.e., equivalent to MethodInfo.ReflectedType) - - - - - Gets a value indicating whether the method is abstract. - - - - - Gets a value indicating whether the method is static. - - - - - Gets the underlying for the method, if available. - - - - - Gets the name of the method. - - - - - Gets the fully qualified type name of the return type. - - - - - Gets the fully qualified type name of the type that this method belongs to. If - using reflection, this should be the ReflectedType. - - - - - Represents information about a type. - - - - - Gets all the custom attributes for the type that are of the given attribute type. - - The type of the attribute - The matching attributes that decorate the type - - - - Gets a test method by name. - - The name of the method - The method, if it exists; null, otherwise. - - - - Gets all the methods - - - - - - Determines if the type has at least one instance of the given attribute type. - - The type of the attribute - True if the type has at least one instance of the given attribute type; false, otherwise - - - - Determines if the type implements the given interface. - - The type of the interface - True if the type implements the given interface; false, otherwise - - - - Gets a value indicating whether the type is abstract. - - - - - Gets a value indicating whether the type is sealed. - - - - - Gets the underlying object, if available. - - - - - Utility class which inspects methods for test information - - - - - Gets the display name. - - The method to be inspected - The display name - - - - Gets the skip reason from a test method. - - The method to be inspected - The skip reason - - - - Gets the test commands for a test method. - - The method to be inspected - The objects for the test method - - - - Gets the timeout value for a test method. - - The method to be inspected - The timeout, in milliseconds - - - - Gets the traits on a test method. - - The method to be inspected - A dictionary of the traits - - - - Determines whether a test method has a timeout. - - The method to be inspected - True if the method has a timeout; false, otherwise - - - - Determines whether a test method has traits. - - The method to be inspected - True if the method has traits; false, otherwise - - - - Determines whether a test method should be skipped. - - The method to be inspected - True if the method should be skipped; false, otherwise - - - - Determines whether a method is a test method. A test method must be decorated - with the (or derived class) and must not be abstract. - - The method to be inspected - True if the method is a test method; false, otherwise - - - - Wrapper to implement and using reflection. - - - - - Converts an into an using reflection. - - - - - - - Converts a into an using reflection. - - The method to wrap - The wrapper - - - - Converts a into an using reflection. - - The type to wrap - The wrapper - - - - Utility class which inspects types for test information - - - - - Determines if a type contains any test methods - - The type to be inspected - True if the class contains any test methods; false, otherwise - - - - Retrieves the type to run the test class with from the , if present. - - The type to be inspected - The type of the test class runner, if present; null, otherwise - - - - Retrieves a list of the test methods from the test class. - - The type to be inspected - The test methods - - - - Determines if the test class has a applied to it. - - The type to be inspected - True if the test class has a run with attribute; false, otherwise - - - - Determines if the type implements . - - The type to be inspected - True if the type implements ; false, otherwise - - - - Determines whether the specified type is abstract. - - The type. - - true if the specified type is abstract; otherwise, false. - - - - - Determines whether the specified type is static. - - The type. - - true if the specified type is static; otherwise, false. - - - - - Determines if a class is a test class. - - The type to be inspected - True if the type is a test class; false, otherwise - - - - Attribute that is applied to a method to indicate that it is a fact that should be run - by the test runner. It can also be extended to support a customized definition of a - test method. - - - - - Creates instances of which represent individual intended - invocations of the test method. - - The method under test - An enumerator through the desired test method invocations - - - - Enumerates the test commands represented by this test method. Derived classes should - override this method to return instances of , one per execution - of a test method. - - The test method - The test commands which will execute the test runs for the given method - - - - Gets the name of the test to be used when the test is skipped. Defaults to - null, which will cause the fully qualified test name to be used. - - - - - Obsolete. Please use the property instead. - - - - - Marks the test so that it will not be run, and gets or sets the skip reason - - - - - Marks the test as failing if it does not finish running within the given time - period, in milliseconds; set to 0 or less to indicate the method has no timeout - - - - - Exception thrown when code unexpectedly fails to throw an exception. - - - - - Creates a new instance of the class. Call this constructor - when no exception was thrown. - - The type of the exception that was expected - - - - Creates a new instance of the class. Call this constructor - when an exception of the wrong type was thrown. - - The type of the exception that was expected - The actual exception that was thrown - - - - - - - THIS CONSTRUCTOR IS FOR UNIT TESTING PURPOSES ONLY. - - - - - - - - Gets a string representation of the frames on the call stack at the time the current exception was thrown. - - A string that describes the contents of the call stack, with the most recent method call appearing first. - - - - Exception thrown when a test method exceeds the given timeout value - - - - - Creates a new instance of the class. - - The timeout value, in milliseconds - - - - - - - Exception thrown when a value is unexpectedly false. - - - - - Creates a new instance of the class. - - The user message to be displayed, or null for the default message - - - - - - diff --git a/LibGit2Sharp.Tests/ArchiveFixture.cs b/LibGit2Sharp.Tests/ArchiveFixture.cs index e070db85d..19860ca0b 100644 --- a/LibGit2Sharp.Tests/ArchiveFixture.cs +++ b/LibGit2Sharp.Tests/ArchiveFixture.cs @@ -11,12 +11,15 @@ public class ArchiveFixture : BaseFixture [Fact] public void CanArchiveATree() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup("581f9824ecaf824221bd36edf5430f2739a7c4f5"); var archiver = new MockArchiver(); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + repo.ObjectDatabase.Archive(tree, archiver); var expected = new ArrayList @@ -29,14 +32,15 @@ public void CanArchiveATree() }; Assert.Equal(expected, archiver.Files); Assert.Null(archiver.ReceivedCommitSha); - Assert.InRange(archiver.ModificationTime, DateTimeOffset.UtcNow.Subtract(TimeSpan.FromMilliseconds(100)), DateTimeOffset.UtcNow); + Assert.InRange(archiver.ModificationTime, before, DateTimeOffset.UtcNow); } } [Fact] public void CanArchiveACommit() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); @@ -61,10 +65,13 @@ public void CanArchiveACommit() [Fact] public void ArchivingANullTreeOrCommitThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.ObjectDatabase.Archive((Commit)null, null)); - Assert.Throws(() => repo.ObjectDatabase.Archive((Tree)null, null)); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Commit), default(ArchiverBase))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Commit), default(string))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Tree), default(ArchiverBase))); + Assert.Throws(() => repo.ObjectDatabase.Archive(default(Tree), default(string))); } } diff --git a/LibGit2Sharp.Tests/ArchiveTarFixture.cs b/LibGit2Sharp.Tests/ArchiveTarFixture.cs index a59b23604..247a9a3b0 100644 --- a/LibGit2Sharp.Tests/ArchiveTarFixture.cs +++ b/LibGit2Sharp.Tests/ArchiveTarFixture.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Text; using LibGit2Sharp.Tests.TestHelpers; @@ -11,7 +10,7 @@ public class ArchiveTarFixture : BaseFixture [Fact] public void CanArchiveACommitWithDirectoryAsTar() { - var path = CloneBareTestRepo(); + var path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { // This tests generates an archive of the bare test repo, and compares it with @@ -26,13 +25,13 @@ public void CanArchiveACommitWithDirectoryAsTar() var commit = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); var scd = BuildSelfCleaningDirectory(); - var archivePath = Path.Combine(scd.RootedDirectoryPath, Guid.NewGuid() + ".tar"); + var archivePath = Path.Combine(scd.RootedDirectoryPath, Path.GetRandomFileName() + ".tar"); Directory.CreateDirectory(scd.RootedDirectoryPath); repo.ObjectDatabase.Archive(commit, archivePath); - using (var expectedStream = new StreamReader(Path.Combine(ResourcesDirectory.FullName, "expected_archives/commit_with_directory.tar"))) - using (var actualStream = new StreamReader(archivePath)) + using (var expectedStream = new StreamReader(File.OpenRead(Path.Combine(ResourcesDirectory.FullName, "expected_archives/commit_with_directory.tar")))) + using (var actualStream = new StreamReader(File.OpenRead(archivePath))) { string expected = expectedStream.ReadToEnd(); string actual = actualStream.ReadToEnd(); diff --git a/LibGit2Sharp.Tests/AttributesFixture.cs b/LibGit2Sharp.Tests/AttributesFixture.cs index 5092518b3..3ac8326d3 100644 --- a/LibGit2Sharp.Tests/AttributesFixture.cs +++ b/LibGit2Sharp.Tests/AttributesFixture.cs @@ -9,8 +9,7 @@ public class AttributesFixture : BaseFixture [Fact] public void StagingHonorsTheAttributesFiles() { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFile(repo); @@ -30,7 +29,7 @@ private static void AssertNormalization(IRepository repo, string filename, bool Touch(repo.Info.WorkingDirectory, filename, sb.ToString()); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); IndexEntry entry = repo.Index[filename]; Assert.NotNull(entry); diff --git a/LibGit2Sharp.Tests/BlameFixture.cs b/LibGit2Sharp.Tests/BlameFixture.cs index b0fcf77fe..8cefcfb45 100644 --- a/LibGit2Sharp.Tests/BlameFixture.cs +++ b/LibGit2Sharp.Tests/BlameFixture.cs @@ -9,7 +9,7 @@ public class BlameFixture : BaseFixture { private static void AssertCorrectHeadBlame(BlameHunkCollection blame) { - Assert.Equal(1, blame.Count()); + Assert.Single(blame); Assert.Equal(0, blame[0].FinalStartLineNumber); Assert.Equal("schacon@gmail.com", blame[0].FinalSignature.Email); Assert.Equal("4a202b3", blame[0].FinalCommit.Id.ToString(7)); @@ -22,7 +22,8 @@ private static void AssertCorrectHeadBlame(BlameHunkCollection blame) [Fact] public void CanBlameSimply() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { AssertCorrectHeadBlame(repo.Blame("README")); } @@ -31,20 +32,22 @@ public void CanBlameSimply() [Fact] public void CanBlameFromADifferentCommit() { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + string path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { // File doesn't exist at HEAD - Assert.Throws(() => repo.Blame("ancestor-only.txt")); + Assert.Throws(() => repo.Blame("ancestor-only.txt")); var blame = repo.Blame("ancestor-only.txt", new BlameOptions { StartingAt = "9107b30" }); - Assert.Equal(1, blame.Count()); + Assert.Single(blame); } } [Fact] public void ValidatesLimits() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blame = repo.Blame("README"); @@ -56,25 +59,27 @@ public void ValidatesLimits() [Fact] public void CanBlameFromVariousTypes() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions {StartingAt = "HEAD" })); - AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions {StartingAt = repo.Head })); - AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions {StartingAt = repo.Head.Tip })); - AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions {StartingAt = repo.Branches["master"]})); + AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions { StartingAt = "HEAD" })); + AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions { StartingAt = repo.Head })); + AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions { StartingAt = repo.Head.Tip })); + AssertCorrectHeadBlame(repo.Blame("README", new BlameOptions { StartingAt = repo.Branches["master"] })); } } [Fact] public void CanStopBlame() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { // $ git blame .\new.txt // 9fd738e8 (Scott Chacon 2010-05-24 10:19:19 -0700 1) my new file // (be3563a comes after 9fd738e8) - var blame = repo.Blame("new.txt", new BlameOptions {StoppingAt = "be3563a"}); - Assert.True(blame[0].FinalCommit.Sha.StartsWith("be3563a")); + var blame = repo.Blame("new.txt", new BlameOptions { StoppingAt = "be3563a" }); + Assert.StartsWith("be3563a", blame[0].FinalCommit.Sha); } } } diff --git a/LibGit2Sharp.Tests/BlobFixture.cs b/LibGit2Sharp.Tests/BlobFixture.cs index f615aed18..314dea379 100644 --- a/LibGit2Sharp.Tests/BlobFixture.cs +++ b/LibGit2Sharp.Tests/BlobFixture.cs @@ -3,7 +3,6 @@ using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,9 +11,11 @@ public class BlobFixture : BaseFixture [Fact] public void CanGetBlobAsText() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + Assert.False(blob.IsMissing); var text = blob.GetContentText(); @@ -30,12 +31,13 @@ public void CanGetBlobAsFilteredText(string autocrlf, string expectedText) { SkipIfNotSupported(autocrlf); - var path = CloneBareTestRepo(); + var path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("core.autocrlf", autocrlf); var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + Assert.False(blob.IsMissing); var text = blob.GetContentText(new FilteringOptions("foo.txt")); @@ -43,6 +45,7 @@ public void CanGetBlobAsFilteredText(string autocrlf, string expectedText) } } +#if NETFRAMEWORK //UTF-7 is disabled in .NET 5+ [Theory] [InlineData("ascii", 4, "31 32 33 34")] [InlineData("utf-7", 4, "31 32 33 34")] @@ -52,7 +55,7 @@ public void CanGetBlobAsFilteredText(string autocrlf, string expectedText) [InlineData("utf-32", 20, "FF FE 00 00 31 00 00 00 32 00 00 00 33 00 00 00 34 00 00 00")] public void CanGetBlobAsTextWithVariousEncodings(string encodingName, int expectedContentBytes, string expectedUtf7Chars) { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var bomFile = "bom.txt"; @@ -62,10 +65,11 @@ public void CanGetBlobAsTextWithVariousEncodings(string encodingName, int expect var bomPath = Touch(repo.Info.WorkingDirectory, bomFile, content, encoding); Assert.Equal(expectedContentBytes, File.ReadAllBytes(bomPath).Length); - repo.Index.Stage(bomFile); + Commands.Stage(repo, bomFile); var commit = repo.Commit("bom", Constants.Signature, Constants.Signature); var blob = (Blob)commit.Tree[bomFile].Target; + Assert.False(blob.IsMissing); Assert.Equal(expectedContentBytes, blob.Size); using (var stream = blob.GetContentStream()) { @@ -82,13 +86,16 @@ public void CanGetBlobAsTextWithVariousEncodings(string encodingName, int expect Assert.Equal(expectedUtf7Chars, string.Join(" ", utf7Chars)); } } +#endif [Fact] public void CanGetBlobSize() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + Assert.False(blob.IsMissing); Assert.Equal(10, blob.Size); } } @@ -96,19 +103,23 @@ public void CanGetBlobSize() [Fact] public void CanLookUpBlob() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); Assert.NotNull(blob); + Assert.False(blob.IsMissing); } } [Fact] public void CanReadBlobStream() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + Assert.False(blob.IsMissing); var contentStream = blob.GetContentStream(); Assert.Equal(blob.Size, contentStream.Length); @@ -129,12 +140,13 @@ public void CanReadBlobFilteredStream(string autocrlf, string expectedContent) { SkipIfNotSupported(autocrlf); - var path = CloneBareTestRepo(); + var path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("core.autocrlf", autocrlf); var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + Assert.False(blob.IsMissing); var contentStream = blob.GetContentStream(new FilteringOptions("foo.txt")); Assert.Equal(expectedContent.Length, contentStream.Length); @@ -153,12 +165,13 @@ public void CanReadBlobFilteredStreamOfUnmodifiedBinary() { var binaryContent = new byte[] { 0, 1, 2, 3, 4, 5 }; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { using (var stream = new MemoryStream(binaryContent)) { Blob blob = repo.ObjectDatabase.CreateBlob(stream); + Assert.False(blob.IsMissing); using (var filtered = blob.GetContentStream(new FilteringOptions("foo.txt"))) { @@ -181,16 +194,17 @@ public void CanStageAFileGeneratedFromABlobContentStream() var sb = new StringBuilder(); for (int j = 0; j < 2000; j++) { - sb.Append(((i + 1)*(j + 1)).ToString("X8")); + sb.Append(((i + 1) * (j + 1)).ToString("X8")); } File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "small.txt"), sb.ToString()); } - repo.Index.Stage("small.txt"); + Commands.Stage(repo, "small.txt"); IndexEntry entry = repo.Index["small.txt"]; Assert.Equal("baae1fb3760a73481ced1fa03dc15614142c19ef", entry.Id.Sha); var blob = repo.Lookup(entry.Id.Sha); + Assert.False(blob.IsMissing); using (Stream stream = blob.GetContentStream()) using (Stream file = File.OpenWrite(Path.Combine(repo.Info.WorkingDirectory, "small.fromblob.txt"))) @@ -198,7 +212,7 @@ public void CanStageAFileGeneratedFromABlobContentStream() CopyStream(stream, file); } - repo.Index.Stage("small.fromblob.txt"); + Commands.Stage(repo, "small.fromblob.txt"); IndexEntry newentry = repo.Index["small.fromblob.txt"]; Assert.Equal("baae1fb3760a73481ced1fa03dc15614142c19ef", newentry.Id.Sha); @@ -208,16 +222,42 @@ public void CanStageAFileGeneratedFromABlobContentStream() [Fact] public void CanTellIfTheBlobContentLooksLikeBinary() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - Assert.Equal(false, blob.IsBinary); + Assert.False(blob.IsMissing); + Assert.False(blob.IsBinary); + } + } + + [Fact] + public void CanTellIfABlobIsMissing() + { + string repoPath = SandboxBareTestRepo(); + + // Manually delete the objects directory to simulate a partial clone + Directory.Delete(Path.Combine(repoPath, "objects", "a8"), true); + + using (var repo = new Repository(repoPath)) + { + // Look up for the tree that reference the blob which is now missing + var tree = repo.Lookup("fd093bff70906175335656e6ce6ae05783708765"); + var blob = (Blob)tree["README"].Target; + + Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", blob.Sha); + Assert.NotNull(blob); + Assert.True(blob.IsMissing); + Assert.Throws(() => blob.Size); + Assert.Throws(() => blob.IsBinary); + Assert.Throws(() => blob.GetContentText()); + Assert.Throws(() => blob.GetContentText(new FilteringOptions("foo.txt"))); } } private static void SkipIfNotSupported(string autocrlf) { - InconclusiveIf(() => autocrlf == "true" && IsRunningOnLinux(), "Non-Windows does not support core.autocrlf = true"); + InconclusiveIf(() => autocrlf == "true" && Constants.IsRunningOnUnix, "Non-Windows does not support core.autocrlf = true"); } } } diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index adfaeceb3..88247e256 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -4,7 +4,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -17,16 +16,18 @@ public class BranchFixture : BaseFixture [InlineData("Ångström")] public void CanCreateBranch(string name) { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string committish = "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); - Assert.Equal(name, newBranch.Name); + Assert.Equal(name, newBranch.FriendlyName); Assert.Equal("refs/heads/" + name, newBranch.CanonicalName); Assert.NotNull(newBranch.Tip); Assert.Equal(committish, newBranch.Tip.Sha); @@ -36,21 +37,36 @@ public void CanCreateBranch(string name) // when they're read back: // - from InlineData: C5-00-6E-00-67-00-73-00-74-00-72-00-F6-00-6D-00 // - from filesystem: 41-00-0A-03-6E-00-67-00-73-00-74-00-72-00-6F-00-08-03-6D-00 - Assert.NotNull(repo.Branches.SingleOrDefault(p => p.Name.Normalize() == name)); + Assert.NotNull(repo.Branches.SingleOrDefault(p => p.FriendlyName.Normalize() == name)); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + committish, + null, newBranch.Tip.Id, - "branch: Created from " + committish); + Constants.Identity, before); - repo.Branches.Remove(newBranch.Name); + repo.Branches.Remove(newBranch.FriendlyName); Assert.Null(repo.Branches[name]); } } + [Theory] + [InlineData("32eab9cb1f450b5fe7ab663462b77d7f4b703344")] + public void CanHeadBeDetached(string commit) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.False(repo.Info.IsHeadDetached); + Commands.Checkout(repo, commit); + Assert.True(repo.Info.IsHeadDetached); + } + } + [Fact] public void CanCreateAnUnbornBranch() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // No branch named orphan @@ -70,7 +86,7 @@ public void CanCreateAnUnbornBranch() Commit c = repo.Commit("New initial root commit", Constants.Signature, Constants.Signature); // Ensure this commit has no parent - Assert.Equal(0, c.Parents.Count()); + Assert.Empty(c.Parents); // The branch now exists... Branch orphan = repo.Branches["orphan"]; @@ -85,21 +101,25 @@ public void CanCreateAnUnbornBranch() [Fact] public void CanCreateBranchUsingAbbreviatedSha() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string name = "unit_test"; const string committish = "be3563a"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.Equal("refs/heads/" + name, newBranch.CanonicalName); Assert.Equal("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", newBranch.Tip.Sha); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + committish, + null, newBranch.Tip.Id, - "branch: Created from " + committish); + Constants.Identity, before); } } @@ -108,26 +128,31 @@ public void CanCreateBranchUsingAbbreviatedSha() [InlineData("master")] public void CanCreateBranchFromImplicitHead(string headCommitOrBranchSpec) { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); - repo.Checkout(headCommitOrBranchSpec); + Commands.Checkout(repo, headCommitOrBranchSpec); const string name = "unit_test"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name); Assert.NotNull(newBranch); - Assert.Equal(name, newBranch.Name); + Assert.Equal(name, newBranch.FriendlyName); Assert.Equal("refs/heads/" + name, newBranch.CanonicalName); Assert.False(newBranch.IsCurrentRepositoryHead); Assert.NotNull(newBranch.Tip); Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newBranch.Tip.Sha); - Assert.NotNull(repo.Branches.SingleOrDefault(p => p.Name == name)); + Assert.NotNull(repo.Branches.SingleOrDefault(p => p.FriendlyName == name)); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + headCommitOrBranchSpec, + null, newBranch.Tip.Id, - "branch: Created from " + headCommitOrBranchSpec); + Constants.Identity, before); } } @@ -136,62 +161,76 @@ public void CanCreateBranchFromImplicitHead(string headCommitOrBranchSpec) [InlineData("master")] public void CanCreateBranchFromExplicitHead(string headCommitOrBranchSpec) { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); - repo.Checkout(headCommitOrBranchSpec); + Commands.Checkout(repo, headCommitOrBranchSpec); const string name = "unit_test"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, "HEAD"); Assert.NotNull(newBranch); Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newBranch.Tip.Sha); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from HEAD", + null, newBranch.Tip.Id, - "branch: Created from " + headCommitOrBranchSpec); + Constants.Identity, before); } } [Fact] public void CanCreateBranchFromCommit() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string name = "unit_test"; var commit = repo.Lookup("HEAD"); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, commit); Assert.NotNull(newBranch); Assert.Equal("4c062a6361ae6959e06292c1fa5e2822d9c96345", newBranch.Tip.Sha); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + newBranch.Tip.Sha, + null, newBranch.Tip.Id, - "branch: Created from " + newBranch.Tip.Sha); + Constants.Identity, before); } } [Fact] public void CanCreateBranchFromRevparseSpec() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string name = "revparse_branch"; const string committish = "master~2"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); Assert.Equal("9fd738e8f7967c078dceed8190330fc8648ee56a", newBranch.Tip.Sha); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + committish, + null, newBranch.Tip.Id, - "branch: Created from " + committish); + Constants.Identity, before); } } @@ -200,27 +239,31 @@ public void CanCreateBranchFromRevparseSpec() [InlineData("refs/tags/test")] public void CreatingABranchFromATagPeelsToTheCommit(string committish) { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string name = "i-peel-tag"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.CreateBranch(name, committish); Assert.NotNull(newBranch); Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", newBranch.Tip.Sha); AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + committish, + null, newBranch.Tip.Id, - "branch: Created from " + committish); + Constants.Identity, before); } } [Fact] public void CreatingABranchTriggersTheCreationOfADirectReference() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Branch newBranch = repo.CreateBranch("clone-of-master"); @@ -231,45 +274,49 @@ public void CreatingABranchTriggersTheCreationOfADirectReference() Reference reference = repo.Refs[newBranch.CanonicalName]; Assert.NotNull(reference); - Assert.IsType(typeof(DirectReference), reference); + Assert.IsType(reference); } } [Fact] public void CreatingABranchFromANonCommitObjectThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string name = "sorry-dude-i-do-not-do-blobs-nor-trees"; - Assert.Throws(() => repo.CreateBranch(name, "refs/tags/point_to_blob")); - Assert.Throws(() => repo.CreateBranch(name, "53fc32d")); - Assert.Throws(() => repo.CreateBranch(name, "0266163")); + Assert.Throws(() => repo.CreateBranch(name, "refs/tags/point_to_blob")); + Assert.Throws(() => repo.CreateBranch(name, "53fc32d")); + Assert.Throws(() => repo.CreateBranch(name, "0266163")); } } [Fact] public void CreatingBranchWithUnknownNamedTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Branches.Add("my_new_branch", "my_old_branch")); + Assert.Throws(() => repo.Branches.Add("my_new_branch", "my_old_branch")); } } [Fact] public void CreatingBranchWithUnknownShaTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Branches.Add("my_new_branch", Constants.UnknownSha)); - Assert.Throws(() => repo.Branches.Add("my_new_branch", Constants.UnknownSha.Substring(0, 7))); + Assert.Throws(() => repo.Branches.Add("my_new_branch", Constants.UnknownSha)); + Assert.Throws(() => repo.Branches.Add("my_new_branch", Constants.UnknownSha.Substring(0, 7))); } } [Fact] public void CreatingBranchWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Branches.Add(null, repo.Head.CanonicalName)); Assert.Throws(() => repo.Branches.Add(string.Empty, repo.Head.CanonicalName)); @@ -282,9 +329,10 @@ public void CreatingBranchWithBadParamsThrows() [Fact] public void CanListAllBranches() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(expectedBranches, SortedBranches(repo.Branches, b => b.Name)); + Assert.Equal(expectedBranches, SortedBranches(repo.Branches, b => b.FriendlyName)); Assert.Equal(5, repo.Branches.Count()); } @@ -293,7 +341,7 @@ public void CanListAllBranches() [Fact] public void CanListBranchesWithRemoteAndLocalBranchWithSameShortName() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Create a local branch with the same short name as a remote branch. @@ -305,14 +353,15 @@ public void CanListBranchesWithRemoteAndLocalBranchWithSameShortName() }; Assert.Equal(expectedWdBranches, - SortedBranches(repo.Branches.Where(b => !b.IsRemote), b => b.Name)); + SortedBranches(repo.Branches.Where(b => !b.IsRemote), b => b.FriendlyName)); } } [Fact] public void CanListAllBranchesWhenGivenWorkingDir() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var expectedWdBranches = new[] { @@ -321,14 +370,15 @@ public void CanListAllBranchesWhenGivenWorkingDir() "origin/test" }; - Assert.Equal(expectedWdBranches, SortedBranches(repo.Branches, b => b.Name)); + Assert.Equal(expectedWdBranches, SortedBranches(repo.Branches, b => b.FriendlyName)); } } [Fact] public void CanListAllBranchesIncludingRemoteRefs() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var expectedBranchesIncludingRemoteRefs = new[] { @@ -345,27 +395,29 @@ public void CanListAllBranchesIncludingRemoteRefs() new { Name = "origin/test", Sha = "e90810b8df3e80c413d903f631643c716887138d", IsRemote = true }, }; Assert.Equal(expectedBranchesIncludingRemoteRefs, - SortedBranches(repo.Branches, b => new { b.Name, b.Tip.Sha, b.IsRemote })); + SortedBranches(repo.Branches, b => new { Name = b.FriendlyName, b.Tip.Sha, b.IsRemote })); } } [Fact] public void CanResolveRemote() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; - Assert.Equal(repo.Network.Remotes["origin"], master.Remote); + Assert.Equal("origin", master.RemoteName); } } [Fact] public void RemoteAndUpstreamBranchCanonicalNameForNonTrackingBranchIsNull() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch test = repo.Branches["i-do-numbers"]; - Assert.Null(test.Remote); + Assert.Null(test.RemoteName); Assert.Null(test.UpstreamBranchCanonicalName); } } @@ -374,17 +426,19 @@ public void RemoteAndUpstreamBranchCanonicalNameForNonTrackingBranchIsNull() public void QueryRemoteForLocalTrackingBranch() { // There is not a Remote to resolve for a local tracking branch. - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch trackLocal = repo.Branches["track-local"]; - Assert.Null(trackLocal.Remote); + Assert.Null(trackLocal.RemoteName); } } [Fact] public void QueryUpstreamBranchCanonicalNameForLocalTrackingBranch() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch trackLocal = repo.Branches["track-local"]; Assert.Equal("refs/heads/master", trackLocal.UpstreamBranchCanonicalName); @@ -394,47 +448,46 @@ public void QueryUpstreamBranchCanonicalNameForLocalTrackingBranch() [Fact] public void QueryRemoteForRemoteBranch() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var master = repo.Branches["origin/master"]; - Assert.Equal(repo.Network.Remotes["origin"], master.Remote); + Assert.Equal("origin", master.RemoteName); } } [Fact] public void QueryUnresolvableRemoteForRemoteBranch() { - var path = CloneStandardTestRepo(); - var fetchRefSpecs = new string[] { "+refs/heads/notfound/*:refs/remotes/origin/notfound/*" }; - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Update the remote config such that the remote for a // remote branch cannot be resolved Remote remote = repo.Network.Remotes["origin"]; Assert.NotNull(remote); - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); Branch branch = repo.Branches["refs/remotes/origin/master"]; Assert.NotNull(branch); Assert.True(branch.IsRemote); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); } } [Fact] public void QueryAmbigousRemoteForRemoteBranch() { - var path = CloneStandardTestRepo(); - - var fetchRefSpec = "+refs/heads/*:refs/remotes/origin/*"; - var url = "http://github.com/libgit2/TestGitRepository"; + const string fetchRefSpec = "+refs/heads/*:refs/remotes/origin/*"; + const string url = "http://github.com/libgit2/TestGitRepository"; - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Add a second remote so that it is ambiguous which remote // the remote-tracking branch tracks. @@ -445,22 +498,23 @@ public void QueryAmbigousRemoteForRemoteBranch() Assert.NotNull(branch); Assert.True(branch.IsRemote); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); } } [Fact] public void CanLookupABranchByItsCanonicalName() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch branch = repo.Branches["refs/heads/br2"]; Assert.NotNull(branch); - Assert.Equal("br2", branch.Name); + Assert.Equal("br2", branch.FriendlyName); Branch branch2 = repo.Branches["refs/heads/br2"]; Assert.NotNull(branch2); - Assert.Equal("br2", branch2.Name); + Assert.Equal("br2", branch2.FriendlyName); Assert.Equal(branch, branch2); Assert.True((branch2 == branch)); @@ -470,12 +524,13 @@ public void CanLookupABranchByItsCanonicalName() [Fact] public void CanLookupLocalBranch() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.NotNull(master); Assert.False(master.IsRemote); - Assert.Equal("master", master.Name); + Assert.Equal("master", master.FriendlyName); Assert.Equal("refs/heads/master", master.CanonicalName); Assert.True(master.IsCurrentRepositoryHead); Assert.Equal("4c062a6361ae6959e06292c1fa5e2822d9c96345", master.Tip.Sha); @@ -485,7 +540,7 @@ public void CanLookupLocalBranch() [Fact] public void CanLookupABranchWhichNameIsMadeOfNon7BitsAsciiCharacters() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string name = "Ångström"; @@ -501,7 +556,8 @@ public void CanLookupABranchWhichNameIsMadeOfNon7BitsAsciiCharacters() [Fact] public void LookingOutABranchByNameWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch branch; Assert.Throws(() => branch = repo.Branches[null]); @@ -519,10 +575,10 @@ public void CanGetInformationFromUnbornBranch() var head = repo.Head; Assert.Equal("refs/heads/master", head.CanonicalName); - Assert.Equal(0, head.Commits.Count()); + Assert.Empty(head.Commits); Assert.True(head.IsCurrentRepositoryHead); Assert.False(head.IsRemote); - Assert.Equal("master", head.Name); + Assert.Equal("master", head.FriendlyName); Assert.Null(head.Tip); Assert.Null(head["huh?"]); @@ -539,12 +595,12 @@ public void CanGetInformationFromUnbornBranch() [Fact] public void CanGetTrackingInformationFromBranchSharingNoHistoryWithItsTrackedBranch() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; const string logMessage = "update target message"; - repo.Refs.UpdateTarget("refs/remotes/origin/master", "origin/test", Constants.Signature, logMessage); + repo.Refs.UpdateTarget("refs/remotes/origin/master", "origin/test", logMessage); Assert.True(master.IsTracking); Assert.NotNull(master.TrackedBranch); @@ -565,7 +621,7 @@ public void CanGetTrackingInformationFromBranchSharingNoHistoryWithItsTrackedBra [Fact] public void TrackingInformationIsEmptyForBranchTrackingPrunedRemoteBranch() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string remoteRef = "refs/remotes/origin/master"; @@ -587,7 +643,8 @@ public void TrackingInformationIsEmptyForBranchTrackingPrunedRemoteBranch() [Fact] public void TrackingInformationIsEmptyForNonTrackingBranch() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch branch = repo.Branches["test"]; Assert.False(branch.IsTracking); @@ -603,7 +660,8 @@ public void TrackingInformationIsEmptyForNonTrackingBranch() [Fact] public void CanGetTrackingInformationForTrackingBranch() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.True(master.IsTracking); @@ -619,7 +677,8 @@ public void CanGetTrackingInformationForTrackingBranch() [Fact] public void CanGetTrackingInformationForLocalTrackingBranch() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var branch = repo.Branches["track-local"]; Assert.True(branch.IsTracking); @@ -635,7 +694,8 @@ public void CanGetTrackingInformationForLocalTrackingBranch() [Fact] public void RenamingARemoteTrackingBranchThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["refs/remotes/origin/master"]; Assert.True(master.IsRemote); @@ -647,7 +707,8 @@ public void RenamingARemoteTrackingBranchThrows() [Fact] public void CanWalkCommitsFromAnotherBranch() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["test"]; Assert.Equal(2, master.Commits.Count()); @@ -660,13 +721,15 @@ public void CanSetTrackedBranch() const string testBranchName = "branchToSetUpstreamInfoFor"; const string trackedBranchName = "refs/remotes/origin/master"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Branch branch = repo.CreateBranch(testBranchName); + Branch trackedBranch = repo.Branches[trackedBranchName]; + Assert.True(trackedBranch.IsRemote); + + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); Assert.False(branch.IsTracking); - Branch trackedBranch = repo.Branches[trackedBranchName]; repo.Branches.Update(branch, b => b.TrackedBranch = trackedBranch.CanonicalName); @@ -681,7 +744,7 @@ public void CanSetTrackedBranch() Assert.True(branch.IsTracking); Assert.Equal(trackedBranch, branch.TrackedBranch); - Assert.Equal(upstreamRemote, branch.Remote); + Assert.Equal("origin", branch.RemoteName); } } @@ -692,14 +755,14 @@ public void SetTrackedBranchForUnreasolvableRemoteThrows() const string trackedBranchName = "refs/remotes/origin/master"; var fetchRefSpecs = new string[] { "+refs/heads/notfound/*:refs/remotes/origin/notfound/*" }; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Modify the fetch spec so that the remote for the remote-tracking branch // cannot be resolved. Remote remote = repo.Network.Remotes["origin"]; Assert.NotNull(remote); - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); // Now attempt to update the tracked branch Branch branch = repo.CreateBranch(testBranchName); @@ -707,7 +770,7 @@ public void SetTrackedBranchForUnreasolvableRemoteThrows() Branch trackedBranch = repo.Branches[trackedBranchName]; - Assert.Throws(() => repo.Branches.Update(branch, + Assert.Throws(() => repo.Branches.Update(branch, b => b.TrackedBranch = trackedBranch.CanonicalName)); } } @@ -720,13 +783,15 @@ public void CanSetUpstreamBranch() const string trackedBranchName = "refs/remotes/origin/master"; const string remoteName = "origin"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Branch branch = repo.CreateBranch(testBranchName); + Branch trackedBranch = repo.Branches[trackedBranchName]; + Assert.True(trackedBranch.IsRemote); + + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); Assert.False(branch.IsTracking); - Branch trackedBranch = repo.Branches[trackedBranchName]; Branch updatedBranch = repo.Branches.Update(branch, b => b.Remote = remoteName, b => b.UpstreamBranch = upstreamBranchName); @@ -740,7 +805,7 @@ public void CanSetUpstreamBranch() Assert.True(updatedBranch.IsTracking); Assert.Equal(trackedBranch, updatedBranch.TrackedBranch); Assert.Equal(upstreamBranchName, updatedBranch.UpstreamBranchCanonicalName); - Assert.Equal(upstreamRemote, updatedBranch.Remote); + Assert.Equal(remoteName, updatedBranch.RemoteName); } } @@ -750,13 +815,14 @@ public void CanSetLocalTrackedBranch() const string testBranchName = "branchToSetUpstreamInfoFor"; const string localTrackedBranchName = "refs/heads/master"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Branch branch = repo.CreateBranch(testBranchName); - Assert.False(branch.IsTracking); - Branch trackedBranch = repo.Branches[localTrackedBranchName]; + Assert.False(trackedBranch.IsRemote); + + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); + Assert.False(branch.IsTracking); repo.Branches.Update(branch, b => b.TrackedBranch = trackedBranch.CanonicalName); @@ -766,7 +832,7 @@ public void CanSetLocalTrackedBranch() // Branches that track the local remote do not have the "Remote" property set. // Verify (through the configuration entry) that the local remote is set as expected. - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); ConfigurationEntry remoteConfigEntry = repo.Config.Get("branch", testBranchName, "remote"); Assert.NotNull(remoteConfigEntry); Assert.Equal(".", remoteConfigEntry.Value); @@ -788,14 +854,16 @@ public void CanUnsetTrackedBranch() const string testBranchName = "branchToSetUpstreamInfoFor"; const string trackedBranchName = "refs/remotes/origin/master"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Branch branch = repo.CreateBranch(testBranchName); + Branch trackedBranch = repo.Branches[trackedBranchName]; + + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); Assert.False(branch.IsTracking); branch = repo.Branches.Update(branch, - b => b.TrackedBranch = trackedBranchName); + b => b.TrackedBranch = trackedBranch.CanonicalName); // Got the updated branch from the Update() method Assert.True(branch.IsTracking); @@ -805,7 +873,7 @@ public void CanUnsetTrackedBranch() // Verify this is no longer a tracking branch Assert.False(branch.IsTracking); - Assert.Null(branch.Remote); + Assert.Null(branch.RemoteName); Assert.Null(branch.UpstreamBranchCanonicalName); } } @@ -813,7 +881,8 @@ public void CanUnsetTrackedBranch() [Fact] public void CanWalkCommitsFromBranch() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.Equal(7, master.Commits.Count()); @@ -822,7 +891,7 @@ public void CanWalkCommitsFromBranch() private void AssertRemoval(string branchName, bool isRemote, bool shouldPreviouslyAssertExistence) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { if (shouldPreviouslyAssertExistence) @@ -849,7 +918,7 @@ public void CanRemoveAnExistingNamedBranch(string branchName, bool isRemote) [InlineData("origin/br2")] public void CanRemoveAnExistingBranch(string branchName) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Branch curBranch = repo.Branches[branchName]; @@ -860,6 +929,24 @@ public void CanRemoveAnExistingBranch(string branchName) } } + [Fact] + public void CanCreateBranchInDeletedNestedBranchNamespace() + { + const string namespaceName = "level_one"; + string branchWithNamespaceName = string.Join("/", namespaceName, "level_two"); + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Commit commit = repo.Head.Tip; + + Branch branchWithNamespace = repo.Branches.Add(branchWithNamespaceName, commit); + repo.Branches.Remove(branchWithNamespace); + + repo.Branches.Add(namespaceName, commit); + } + } + [Theory] [InlineData("I-donot-exist", false)] [InlineData("me/neither", true)] @@ -871,26 +958,30 @@ public void CanRemoveANonExistingBranch(string branchName, bool isRemote) [Fact] public void RemovingABranchWhichIsTheCurrentHeadThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Branches.Remove(repo.Head.Name)); + Assert.Throws(() => repo.Branches.Remove(repo.Head.FriendlyName)); } } [Fact] public void RemovingABranchWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Branches.Remove(string.Empty)); - Assert.Throws(() => repo.Branches.Remove(null)); + Assert.Throws(() => repo.Branches.Remove(default(string))); + Assert.Throws(() => repo.Branches.Remove(default(Branch))); } } [Fact] public void OnlyOneBranchIsTheHead() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch head = null; @@ -909,7 +1000,7 @@ public void OnlyOneBranchIsTheHead() continue; } - Assert.True(false, string.Format("Both '{0}' and '{1}' appear to be Head.", head.CanonicalName, branch.CanonicalName)); + Assert.Fail(string.Format("Both '{0}' and '{1}' appear to be Head.", head.CanonicalName, branch.CanonicalName)); } Assert.NotNull(head); @@ -919,7 +1010,7 @@ public void OnlyOneBranchIsTheHead() [Fact] public void TwoBranchesPointingAtTheSameCommitAreNotBothCurrent() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Branch master = repo.Branches["refs/heads/master"]; @@ -932,8 +1023,8 @@ public void TwoBranchesPointingAtTheSameCommitAreNotBothCurrent() [Fact] public void CanRenameABranch() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); @@ -941,23 +1032,28 @@ public void CanRenameABranch() var br2 = repo.Branches["br2"]; Assert.NotNull(br2); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.Branches.Rename("br2", "br3"); - Assert.Equal("br3", newBranch.Name); + Assert.Equal("br3", newBranch.FriendlyName); Assert.Null(repo.Branches["br2"]); Assert.NotNull(repo.Branches["br3"]); AssertRefLogEntry(repo, newBranch.CanonicalName, + string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName), + br2.Tip.Id, newBranch.Tip.Id, - string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName)); + Constants.Identity, before); } } [Fact] public void BlindlyRenamingABranchOverAnExistingOneThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Branches.Rename("br2", "test")); } @@ -966,8 +1062,8 @@ public void BlindlyRenamingABranchOverAnExistingOneThrows() [Fact] public void CanRenameABranchWhileOverwritingAnExistingOne() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); @@ -977,8 +1073,10 @@ public void CanRenameABranchWhileOverwritingAnExistingOne() Branch br2 = repo.Branches["br2"]; Assert.NotNull(br2); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Branch newBranch = repo.Branches.Rename("br2", "test", true); - Assert.Equal("test", newBranch.Name); + Assert.Equal("test", newBranch.FriendlyName); Assert.Null(repo.Branches["br2"]); @@ -989,23 +1087,24 @@ public void CanRenameABranchWhileOverwritingAnExistingOne() Assert.Equal(br2.Tip, newTest.Tip); AssertRefLogEntry(repo, newBranch.CanonicalName, - newBranch.Tip.Id, string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName), - test.Tip.Id); + br2.Tip.Id, + newTest.Tip.Id, + Constants.Identity, before); } } [Fact] public void DetachedHeadIsNotATrackingBranch() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); repo.RemoveUntrackedFiles(); string headSha = repo.Head.Tip.Sha; - repo.Checkout(headSha); + Commands.Checkout(repo, headSha); Assert.False(repo.Head.IsTracking); Assert.Null(repo.Head.TrackedBranch); @@ -1026,7 +1125,7 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() using (var emptyRepo = new Repository(repoPath)) { - uri = new Uri(emptyRepo.Info.Path); + uri = new Uri($"file://{emptyRepo.Info.Path}"); } SelfCleaningDirectory scd2 = BuildSelfCleaningDirectory(); @@ -1036,7 +1135,7 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() using (var repo = new Repository(clonedRepoPath)) { Assert.Empty(Directory.GetFiles(scd2.RootedDirectoryPath)); - Assert.Equal(repo.Head.Name, "master"); + Assert.Equal("master", repo.Head.FriendlyName); Assert.Null(repo.Head.Tip); Assert.NotNull(repo.Head.TrackedBranch); @@ -1047,11 +1146,10 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() Assert.Null(repo.Head.TrackingDetails.BehindBy); Assert.Null(repo.Head.TrackingDetails.CommonAncestor); - Assert.NotNull(repo.Head.Remote); - Assert.Equal("origin", repo.Head.Remote.Name); + Assert.Equal("origin", repo.Head.RemoteName); Touch(repo.Info.WorkingDirectory, "a.txt", "a"); - repo.Index.Stage("a.txt"); + Commands.Stage(repo, "a.txt"); repo.Commit("A file", Constants.Signature, Constants.Signature); Assert.NotNull(repo.Head.Tip); @@ -1068,14 +1166,15 @@ public void TrackedBranchExistsFromDefaultConfigInEmptyClone() [Fact] public void RemoteBranchesDoNotTrackAnything() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var branches = repo.Branches.Where(b => b.IsRemote); foreach (var branch in branches) { Assert.True(branch.IsRemote); - Assert.NotNull(branch.Remote); + Assert.NotNull(branch.RemoteName); Assert.False(branch.IsTracking); Assert.Null(branch.TrackedBranch); @@ -1095,33 +1194,53 @@ private static T[] SortedBranches(IEnumerable branches, Func(commitPointer); AssertBelongsToARepository(repo, commit); - Branch detachedHead = checkoutByCommitOrBranchSpec ? repo.Checkout(commitPointer) : repo.Checkout(commit); + Branch detachedHead = checkoutByCommitOrBranchSpec ? Commands.Checkout(repo, commitPointer) : Commands.Checkout(repo, commit); Assert.Equal(repo.Head, detachedHead); Assert.Equal(commit.Sha, detachedHead.Tip.Sha); Assert.True(repo.Head.IsCurrentRepositoryHead); Assert.True(repo.Info.IsHeadDetached); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Assert.True(detachedHead.IsCurrentRepositoryHead); Assert.False(detachedHead.IsRemote); - Assert.Equal(detachedHead.Name, detachedHead.CanonicalName); + Assert.Equal(detachedHead.FriendlyName, detachedHead.CanonicalName); Assert.Equal("(no branch)", detachedHead.CanonicalName); @@ -138,8 +138,8 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer, bool checkoutByCo var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); Assert.Equal(master.Tip.Id, reflogEntry.From); Assert.Equal(commit.Sha, reflogEntry.To.Sha); - Assert.NotNull(reflogEntry.Commiter.Email); - Assert.NotNull(reflogEntry.Commiter.Name); + Assert.NotNull(reflogEntry.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); Assert.Equal(string.Format("checkout: moving from master to {0}", expectedReflogTarget), reflogEntry.Message); } } @@ -156,16 +156,16 @@ public void CheckoutAddsMissingFilesInWorkingDirectory() // Remove the file in master branch // Verify it exists after checking out otherBranch. string fileFullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - repo.Index.Remove(fileFullPath); + Commands.Remove(repo, fileFullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout other_branch Branch otherBranch = repo.Branches[otherBranchName]; Assert.NotNull(otherBranch); - otherBranch.Checkout(); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Assert.Equal(originalFileContent, File.ReadAllText(fileFullPath)); } } @@ -184,16 +184,16 @@ public void CheckoutRemovesExtraFilesInWorkingDirectory() string newFileFullPath = Touch( repo.Info.WorkingDirectory, "b.txt", "hello from master branch!\n"); - repo.Index.Stage(newFileFullPath); + Commands.Stage(repo, newFileFullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout other_branch Branch otherBranch = repo.Branches[otherBranchName]; Assert.NotNull(otherBranch); - otherBranch.Checkout(); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Assert.False(File.Exists(newFileFullPath)); } } @@ -212,16 +212,16 @@ public void CheckoutUpdatesModifiedFilesInWorkingDirectory() string fullPath = Touch( repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); - repo.Index.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout other_branch Branch otherBranch = repo.Branches[otherBranchName]; Assert.NotNull(otherBranch); - otherBranch.Checkout(); + Commands.Checkout(repo, otherBranch); // Verify working directory is updated - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Assert.Equal(originalFileContent, File.ReadAllText(fullPath)); } } @@ -237,7 +237,7 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() // 4) Create conflicting change // 5) Forcefully checkout master - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; @@ -246,7 +246,7 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() // Set the working directory to the current head. ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Create otherBranch from current Head. repo.Branches.Add(otherBranchName, master.Tip); @@ -254,28 +254,28 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() // Add change to master. Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); - repo.Index.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("change in master", Constants.Signature, Constants.Signature); // Checkout otherBranch. - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Add change to otherBranch. Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); - repo.Index.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); // Assert that normal checkout throws exception // for the conflict. - Assert.Throws(() => repo.Checkout(master.CanonicalName)); + Assert.Throws(() => Commands.Checkout(repo, master.CanonicalName)); // Checkout with force option should succeed. - repo.Checkout(master.CanonicalName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force}); + Commands.Checkout(repo, master.CanonicalName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Assert that master branch is checked out. Assert.True(repo.Branches["master"].IsCurrentRepositoryHead); // And that the current index is not dirty. - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); } } @@ -287,7 +287,7 @@ public void CheckingOutWithMergeConflictsThrows() using (var repo = new Repository(repoPath)) { Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello\n"); - repo.Index.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); // Create 2nd branch @@ -295,20 +295,20 @@ public void CheckingOutWithMergeConflictsThrows() // Update file in main Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello from master!\n"); - repo.Index.Stage(originalFilePath); + Commands.Stage(repo, originalFilePath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout branch2 - repo.Checkout("branch2"); + Commands.Checkout(repo, "branch2"); Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello From branch2!\n"); // Assert that checking out master throws // when there are unstaged commits - Assert.Throws(() => repo.Checkout("master")); + Assert.Throws(() => Commands.Checkout(repo, "master")); // And when there are staged commits - repo.Index.Stage(originalFilePath); - Assert.Throws(() => repo.Checkout("master")); + Commands.Stage(repo, originalFilePath); + Assert.Throws(() => Commands.Checkout(repo, "master")); } } @@ -319,10 +319,10 @@ public void CanCancelCheckoutThroughNotifyCallback() using (var repo = new Repository(repoPath)) { - string relativePath = "a.txt"; + const string relativePath = "a.txt"; Touch(repo.Info.WorkingDirectory, relativePath, "Hello\n"); - repo.Index.Stage(relativePath); + Commands.Stage(repo, relativePath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); // Create 2nd branch @@ -330,11 +330,11 @@ public void CanCancelCheckoutThroughNotifyCallback() // Update file in main Touch(repo.Info.WorkingDirectory, relativePath, "Hello from master!\n"); - repo.Index.Stage(relativePath); + Commands.Stage(repo, relativePath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout branch2 - repo.Checkout("branch2"); + Commands.Checkout(repo, "branch2"); // Update the context of a.txt - a.txt will then conflict between branch2 and master. Touch(repo.Info.WorkingDirectory, relativePath, "Hello From branch2!\n"); @@ -348,7 +348,7 @@ public void CanCancelCheckoutThroughNotifyCallback() CheckoutNotifyFlags = CheckoutNotifyFlags.Conflict, }; - Assert.Throws(() => repo.Checkout("master", options)); + Assert.Throws(() => Commands.Checkout(repo, "master", options)); Assert.Equal(relativePath, conflictPath); } } @@ -356,10 +356,11 @@ public void CanCancelCheckoutThroughNotifyCallback() [Fact] public void CheckingOutInABareRepoThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Checkout(repo.Branches["refs/heads/test"])); - Assert.Throws(() => repo.Checkout("refs/heads/test")); + Assert.Throws(() => Commands.Checkout(repo, repo.Branches["refs/heads/test"])); + Assert.Throws(() => Commands.Checkout(repo, "refs/heads/test")); } } @@ -372,27 +373,29 @@ public void CheckingOutAgainstAnUnbornBranchThrows() { Assert.True(repo.Info.IsHeadUnborn); - Assert.Throws(() => repo.Checkout(repo.Head)); + Assert.Throws(() => Commands.Checkout(repo, repo.Head)); } } [Fact] public void CheckingOutANonExistingBranchThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Checkout("i-do-not-exist")); + Assert.Throws(() => Commands.Checkout(repo, "i-do-not-exist")); } } [Fact] public void CheckingOutABranchWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Checkout(string.Empty)); - Assert.Throws(() => repo.Checkout(default(Branch))); - Assert.Throws(() => repo.Checkout(default(string))); + Assert.Throws(() => Commands.Checkout(repo, string.Empty)); + Assert.Throws(() => Commands.Checkout(repo, default(Branch))); + Assert.Throws(() => Commands.Checkout(repo, default(string))); } } @@ -407,7 +410,8 @@ public void CheckingOutThroughBranchCallsCheckoutProgress() bool wasCalled = false; Branch branch = repo.Branches[otherBranchName]; - branch.Checkout(new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); + Commands.Checkout(repo, branch, + new CheckoutOptions { OnCheckoutProgress = (path, completed, total) => wasCalled = true }); Assert.True(wasCalled); } @@ -423,7 +427,7 @@ public void CheckingOutThroughRepositoryCallsCheckoutProgress() PopulateBasicRepository(repo); bool wasCalled = false; - repo.Checkout(otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true}); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true }); Assert.True(wasCalled); } @@ -447,15 +451,15 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri { PopulateBasicRepository(repo); - string relativePathUpdated = "updated.txt"; + const string relativePathUpdated = "updated.txt"; Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text A"); - repo.Index.Stage(relativePathUpdated); + Commands.Stage(repo, relativePathUpdated); repo.Commit("Commit initial update file", Constants.Signature, Constants.Signature); // Create conflicting change - string relativePathConflict = "conflict.txt"; + const string relativePathConflict = "conflict.txt"; Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text A"); - repo.Index.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("Initial commit of conflict.txt and update.txt", Constants.Signature, Constants.Signature); // Create another branch @@ -463,32 +467,32 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri // Make an edit to conflict.txt and update.txt Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text BB"); - repo.Index.Stage(relativePathUpdated); + Commands.Stage(repo, relativePathUpdated); Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text BB"); - repo.Index.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("2nd commit of conflict.txt and update.txt on master branch", Constants.Signature, Constants.Signature); // Checkout other branch - repo.Checkout("newbranch"); + Commands.Checkout(repo, "newbranch"); // Make alternate edits to conflict.txt and update.txt Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text CCC"); - repo.Index.Stage(relativePathUpdated); + Commands.Stage(repo, relativePathUpdated); Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text CCC"); - repo.Index.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); repo.Commit("2nd commit of conflict.txt and update.txt on newbranch", Constants.Signature, Constants.Signature); // make conflicting change to conflict.txt Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text DDDD"); - repo.Index.Stage(relativePathConflict); + Commands.Stage(repo, relativePathConflict); // Create ignored change string relativePathIgnore = Path.Combine("bin", "ignored.txt"); Touch(repo.Info.WorkingDirectory, relativePathIgnore, "ignored file"); // Create untracked change - string relativePathUntracked = "untracked.txt"; + const string relativePathUntracked = "untracked.txt"; Touch(repo.Info.WorkingDirectory, relativePathUntracked, "untracked file"); bool wasCalled = false; @@ -501,7 +505,7 @@ public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, stri CheckoutNotifyFlags = notifyFlags, }; - Assert.Throws(() => repo.Checkout("master", options)); + Assert.Throws(() => Commands.Checkout(repo, "master", options)); Assert.True(wasCalled); Assert.Equal(expectedNotificationPath, actualNotificationPath); @@ -522,14 +526,14 @@ public void CheckoutRetainsUntrackedChanges() string fullPathFileB = Touch(repo.Info.WorkingDirectory, "b.txt", alternateFileContent); // Verify that there is an untracked entry. - Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(fullPathFileB)); + Assert.Single(repo.RetrieveStatus().Untracked); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Verify untracked entry still exists. - Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(fullPathFileB)); + Assert.Single(repo.RetrieveStatus().Untracked); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); } } @@ -546,14 +550,14 @@ public void ForceCheckoutRetainsUntrackedChanges() string fullPathFileB = Touch(repo.Info.WorkingDirectory, "b.txt", alternateFileContent); // Verify that there is an untracked entry. - Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(fullPathFileB)); + Assert.Single(repo.RetrieveStatus().Untracked); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); - repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify untracked entry still exists. - Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(fullPathFileB)); + Assert.Single(repo.RetrieveStatus().Untracked); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(fullPathFileB)); } } @@ -570,14 +574,14 @@ public void CheckoutRetainsUnstagedChanges() string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); // Verify that there is a modified entry. - Assert.Equal(1, repo.Index.RetrieveStatus().Modified.Count()); - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus(fullPathFileA)); + Assert.Single(repo.RetrieveStatus().Modified); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(fullPathFileA)); - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Verify modified entry still exists. - Assert.Equal(1, repo.Index.RetrieveStatus().Modified.Count()); - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus(fullPathFileA)); + Assert.Single(repo.RetrieveStatus().Modified); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(fullPathFileA)); } } @@ -592,17 +596,17 @@ public void CheckoutRetainsStagedChanges() // Generate a staged change. string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); - repo.Index.Stage(fullPathFileA); + Commands.Stage(repo, fullPathFileA); // Verify that there is a staged entry. - Assert.Equal(1, repo.Index.RetrieveStatus().Staged.Count()); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus(fullPathFileA)); + Assert.Single(repo.RetrieveStatus().Staged); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(fullPathFileA)); - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Verify staged entry still exists. - Assert.Equal(1, repo.Index.RetrieveStatus().Staged.Count()); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus(fullPathFileA)); + Assert.Single(repo.RetrieveStatus().Staged); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(fullPathFileA)); } } @@ -621,14 +625,14 @@ public void CheckoutRetainsIgnoredChanges() "bin/some_ignored_file.txt", "hello from this ignored file."); - Assert.Equal(1, repo.Index.RetrieveStatus().Ignored.Count()); + Assert.Single(repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }).Ignored); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); - repo.Checkout(otherBranchName); + Commands.Checkout(repo, otherBranchName); // Verify that the ignored file still exists. - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); Assert.True(File.Exists(ignoredFilePath)); } } @@ -648,14 +652,14 @@ public void ForceCheckoutRetainsIgnoredChanges() "bin/some_ignored_file.txt", "hello from this ignored file."); - Assert.Equal(1, repo.Index.RetrieveStatus().Ignored.Count()); + Assert.Single(repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }).Ignored); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); - repo.Checkout(otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // Verify that the ignored file still exists. - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(ignoredFilePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(ignoredFilePath)); Assert.True(File.Exists(ignoredFilePath)); } } @@ -676,16 +680,16 @@ public void CheckoutBranchSnapshot() // Add commit to master string fullPath = Touch(repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); - repo.Index.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); Assert.False(repo.Info.IsHeadDetached); - initial.Checkout(); + Commands.Checkout(repo, initial); // Head should point at initial commit. Assert.Equal(repo.Head.Tip, initialCommit); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Verify that HEAD is detached. Assert.Equal(repo.Refs["HEAD"].TargetIdentifier, initial.Tip.Sha); @@ -699,7 +703,7 @@ public void CheckoutBranchSnapshot() [InlineData("origin/master")] public void CheckingOutRemoteBranchResultsInDetachedHead(string remoteBranchSpec) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; @@ -708,7 +712,7 @@ public void CheckingOutRemoteBranchResultsInDetachedHead(string remoteBranchSpec // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - repo.Checkout(remoteBranchSpec); + Commands.Checkout(repo, remoteBranchSpec); // Verify that HEAD is detached. Assert.Equal(repo.Refs["HEAD"].TargetIdentifier, repo.Branches["origin/master"].Tip.Sha); @@ -719,7 +723,7 @@ public void CheckingOutRemoteBranchResultsInDetachedHead(string remoteBranchSpec [Fact] public void CheckingOutABranchDoesNotAlterBinaryFiles() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // $ git hash-object square-logo.png @@ -729,7 +733,7 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() // The blob actually exists in the object database with the correct Sha Assert.Equal(expectedSha, repo.Lookup(expectedSha).Sha); - repo.Checkout("refs/heads/logo", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, "refs/heads/logo", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); // The Index has been updated as well with the blob Assert.Equal(expectedSha, repo.Index["square-logo.png"].Id.Sha); @@ -744,29 +748,29 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() [Theory] [InlineData("a447ba2ca8")] - [InlineData("refs/tags/lw")] + [InlineData("lw")] [InlineData("e90810^{}")] public void CheckoutFromDetachedHead(string commitPointer) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); var commitSha = repo.Lookup(commitPointer).Sha; - Branch initialHead = repo.Checkout("6dcf9bf"); + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); - repo.Checkout(commitPointer); + Commands.Checkout(repo, commitPointer); // Assert reflog entry is created var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); Assert.Equal(initialHead.Tip.Id, reflogEntry.From); Assert.Equal(commitSha, reflogEntry.To.Sha); - Assert.NotNull(reflogEntry.Commiter.Email); - Assert.NotNull(reflogEntry.Commiter.Name); + Assert.NotNull(reflogEntry.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); Assert.Equal(string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, commitPointer), reflogEntry.Message); } } @@ -774,23 +778,25 @@ public void CheckoutFromDetachedHead(string commitPointer) [Fact] public void CheckoutBranchFromDetachedHead() { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); - Branch initialHead = repo.Checkout("6dcf9bf"); + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); Assert.True(repo.Info.IsHeadDetached); - Branch newHead = repo.Checkout(repo.Branches["master"], Constants.Signature); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + Branch newHead = Commands.Checkout(repo, repo.Branches["master"]); // Assert reflog entry is created - AssertRefLogEntry(repo, "HEAD", newHead.Tip.Id, - string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, newHead.Name), - initialHead.Tip.Id, Constants.Signature); + AssertRefLogEntry(repo, "HEAD", + string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, newHead.FriendlyName), + initialHead.Tip.Id, newHead.Tip.Id, Constants.Identity, before); } } @@ -799,17 +805,17 @@ public void CheckoutBranchFromDetachedHead() [InlineData("heads/master", "refs/heads/master")] public void CheckoutBranchByShortNameAttachesTheHead(string shortBranchName, string referenceName) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); - repo.Checkout("6dcf9bf"); + Commands.Checkout(repo, "6dcf9bf"); Assert.True(repo.Info.IsHeadDetached); - var branch = repo.Checkout(shortBranchName); + var branch = Commands.Checkout(repo, shortBranchName); Assert.False(repo.Info.IsHeadDetached); Assert.Equal(referenceName, repo.Head.CanonicalName); @@ -820,18 +826,18 @@ public void CheckoutBranchByShortNameAttachesTheHead(string shortBranchName, str [Fact] public void CheckoutPreviousCheckedOutBranch() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); - Branch previousHead = repo.Checkout("i-do-numbers"); - repo.Checkout("diff-test-cases"); + Branch previousHead = Commands.Checkout(repo, "i-do-numbers"); + Commands.Checkout(repo, "diff-test-cases"); //Go back to previous branch checked out - var branch = repo.Checkout(@"@{-1}"); + var branch = Commands.Checkout(repo, @"@{-1}"); Assert.False(repo.Info.IsHeadDetached); Assert.Equal(previousHead.CanonicalName, repo.Head.CanonicalName); @@ -842,45 +848,48 @@ public void CheckoutPreviousCheckedOutBranch() [Fact] public void CheckoutCurrentReference() { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); // Checkout branch - repo.Checkout(master); + Commands.Checkout(repo, master); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + // Checkout in detached mode - repo.Checkout(master.Tip.Sha); + Commands.Checkout(repo, master.Tip.Sha); Assert.True(repo.Info.IsHeadDetached); - AssertRefLogEntry(repo, "HEAD", master.Tip.Id, - string.Format("checkout: moving from master to {0}", master.Tip.Sha), master.Tip.Id); + AssertRefLogEntry(repo, "HEAD", + string.Format("checkout: moving from master to {0}", master.Tip.Sha), + master.Tip.Id, master.Tip.Id, Constants.Identity, before); // Checkout detached "HEAD" => nothing should happen reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); // Checkout attached "HEAD" => nothing should happen - repo.Checkout("master"); + Commands.Checkout(repo, "master"); reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); } @@ -889,24 +898,25 @@ public void CheckoutCurrentReference() [Fact] public void CheckoutLowerCasedHeadThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Checkout("head")); + Assert.Throws(() => Commands.Checkout(repo, "head")); } } [Fact] public void CanCheckoutAttachedHead() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.False(repo.Info.IsHeadDetached); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.False(repo.Info.IsHeadDetached); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.False(repo.Info.IsHeadDetached); } } @@ -914,61 +924,61 @@ public void CanCheckoutAttachedHead() [Fact] public void CanCheckoutDetachedHead() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha); + Commands.Checkout(repo, repo.Head.Tip.Sha); Assert.True(repo.Info.IsHeadDetached); - repo.Checkout(repo.Head); + Commands.Checkout(repo, repo.Head); Assert.True(repo.Info.IsHeadDetached); - repo.Checkout("HEAD"); + Commands.Checkout(repo, "HEAD"); Assert.True(repo.Info.IsHeadDetached); } } [Theory] - [InlineData("master", "6dcf9bf", "readme.txt", FileStatus.Added)] - [InlineData("master", "refs/tags/lw", "readme.txt", FileStatus.Added)] - [InlineData("master", "i-do-numbers", "super-file.txt", FileStatus.Added)] - [InlineData("i-do-numbers", "diff-test-cases", "numbers.txt", FileStatus.Staged)] + [InlineData("master", "6dcf9bf", "readme.txt", FileStatus.NewInIndex)] + [InlineData("master", "refs/tags/lw", "readme.txt", FileStatus.NewInIndex)] + [InlineData("master", "i-do-numbers", "super-file.txt", FileStatus.NewInIndex)] + [InlineData("i-do-numbers", "diff-test-cases", "numbers.txt", FileStatus.ModifiedInIndex)] public void CanCheckoutPath(string originalBranch, string checkoutFrom, string path, FileStatus expectedStatus) { - string repoPath = CloneStandardTestRepo(); + string repoPath = SandboxStandardTestRepo(); using (var repo = new Repository(repoPath)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - repo.Checkout(originalBranch); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Commands.Checkout(repo, originalBranch); + Assert.False(repo.RetrieveStatus().IsDirty); repo.CheckoutPaths(checkoutFrom, new[] { path }); - Assert.Equal(expectedStatus, repo.Index.RetrieveStatus(path)); - Assert.Equal(1, repo.Index.RetrieveStatus().Count()); + Assert.Equal(expectedStatus, repo.RetrieveStatus(path)); + Assert.Single(repo.RetrieveStatus()); } } [Fact] public void CanCheckoutPaths() { - string repoPath = CloneStandardTestRepo(); + string repoPath = SandboxStandardTestRepo(); var checkoutPaths = new[] { "numbers.txt", "super-file.txt" }; using (var repo = new Repository(repoPath)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); repo.CheckoutPaths("i-do-numbers", checkoutPaths); foreach (string checkoutPath in checkoutPaths) { - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(checkoutPath)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(checkoutPath)); } } } @@ -976,21 +986,20 @@ public void CanCheckoutPaths() [Fact] public void CannotCheckoutPathsWithEmptyOrNullPathArgument() { - string repoPath = CloneStandardTestRepo(); + string repoPath = SandboxStandardTestRepo(); using (var repo = new Repository(repoPath)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Passing null 'paths' parameter should throw - Assert.Throws(typeof(ArgumentNullException), - () => repo.CheckoutPaths("i-do-numbers", null)); + Assert.Throws(() => repo.CheckoutPaths("i-do-numbers", null)); // Passing empty list should do nothing repo.CheckoutPaths("i-do-numbers", Enumerable.Empty()); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); } } @@ -999,23 +1008,69 @@ public void CannotCheckoutPathsWithEmptyOrNullPathArgument() [InlineData("1.txt")] public void CanCheckoutPathFromCurrentBranch(string fileName) { - string repoPath = CloneStandardTestRepo(); + string repoPath = SandboxStandardTestRepo(); using (var repo = new Repository(repoPath)) { // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Touch(repo.Info.WorkingDirectory, fileName, "new text file"); - Assert.True(repo.Index.RetrieveStatus().IsDirty); + Assert.True(repo.RetrieveStatus().IsDirty); var opts = new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }; repo.CheckoutPaths("HEAD", new[] { fileName }, opts); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); + } + } + + [Theory] + [InlineData("br2", "origin")] + [InlineData("unique/branch", "another/remote")] + public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndSucceedsIfOnlyOne(string branchName, string expectedRemoteName) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + ResetAndCleanWorkingDirectory(repo); + + // Define another remote + var otherRemote = "another/remote"; + repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository"); + + // Define an extra remote tracking branch that does not conflict + repo.Refs.Add($"refs/remotes/{otherRemote}/unique/branch", repo.Head.Tip.Sha); + + Branch branch = Commands.Checkout(repo, branchName); + + Assert.NotNull(branch); + Assert.True(branch.IsTracking); + Assert.Equal($"refs/remotes/{expectedRemoteName}/{branchName}", branch.TrackedBranch.CanonicalName); + } + } + + [Fact] + public void CheckoutBranchTriesRemoteTrackingBranchAsFallbackAndThrowsIfMoreThanOne() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + ResetAndCleanWorkingDirectory(repo); + + // Define another remote + var otherRemote = "another/remote"; + repo.Network.Remotes.Add(otherRemote, "https://github.com/libgit2/TestGitRepository"); + + // Define remote tracking branches that conflict + var branchName = "conflicting/branch"; + repo.Refs.Add($"refs/remotes/origin/{branchName}", repo.Head.Tip.Sha); + repo.Refs.Add($"refs/remotes/{otherRemote}/{branchName}", repo.Head.Tip.Sha); + + Assert.Throws(() => Commands.Checkout(repo, branchName)); } } @@ -1028,10 +1083,10 @@ private void PopulateBasicRepository(IRepository repo) { // Generate a .gitignore file. string gitIgnoreFilePath = Touch(repo.Info.WorkingDirectory, ".gitignore", "bin"); - repo.Index.Stage(gitIgnoreFilePath); + Commands.Stage(repo, gitIgnoreFilePath); string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); - repo.Index.Stage(fullPathFileA); + Commands.Stage(repo, fullPathFileA); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); diff --git a/LibGit2Sharp.Tests/CherryPickFixture.cs b/LibGit2Sharp.Tests/CherryPickFixture.cs index 8ce6b04a0..f4a383fef 100644 --- a/LibGit2Sharp.Tests/CherryPickFixture.cs +++ b/LibGit2Sharp.Tests/CherryPickFixture.cs @@ -1,9 +1,9 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; -using System; namespace LibGit2Sharp.Tests { @@ -14,12 +14,12 @@ public class CherryPickFixture : BaseFixture [InlineData(false)] public void CanCherryPick(bool fromDetachedHead) { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { if (fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -28,7 +28,7 @@ public void CanCherryPick(bool fromDetachedHead) Assert.Equal(CherryPickStatus.CherryPicked, result.Status); Assert.Equal(cherryPickedCommitId, result.Commit.Id.Sha); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); Assert.Equal(fromDetachedHead, repo.Info.IsHeadDetached); Assert.Equal(commitToMerge.Author, result.Commit.Author); Assert.Equal(Constants.Signature, result.Commit.Committer); @@ -42,11 +42,11 @@ public void CherryPickWithConflictDoesNotCommit() const string secondBranchFileName = "second branch file.txt"; const string sharedBranchFileName = "first+second branch file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -56,7 +56,7 @@ public void CherryPickWithConflictDoesNotCommit() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). AddFileCommitToRepo(repo, secondBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch @@ -66,7 +66,7 @@ public void CherryPickWithConflictDoesNotCommit() Assert.Equal(CherryPickStatus.Conflicts, cherryPickResult.Status); Assert.Null(cherryPickResult.Commit); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); var conflict = repo.Index.Conflicts.First(); var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); @@ -83,7 +83,7 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict const string conflictFile = "a.txt"; const string conflictBranchName = "conflicts"; - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Branch branch = repo.Branches[conflictBranchName]; @@ -126,11 +126,97 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict } } + [Fact] + public void CanCherryPickCommit() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var ours = repo.Head.Tip; + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + var result = repo.ObjectDatabase.CherryPickCommit(commitToMerge, ours, 0, null); + + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CherryPickWithConflictsReturnsConflicts() + { + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + var result = repo.ObjectDatabase.CherryPickCommit(branch.Tip, repo.Head.Tip, 0, null); + + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + Assert.NotEmpty(result.Conflicts); + + } + } + + [Fact] + public void CanCherryPickCommitIntoIndex() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var ours = repo.Head.Tip; + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(commitToMerge, ours, 0, null)) + { + var tree = index.WriteToTree(); + Assert.Equal(commitToMerge.Tree.Id, tree.Id); + } + } + } + + [Fact] + public void CanCherryPickIntoIndexWithConflicts() + { + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + using (TransientIndex index = repo.ObjectDatabase.CherryPickCommitIntoIndex(branch.Tip, repo.Head.Tip, 0, null)) + { + Assert.False(index.IsFullyMerged); + + var conflict = index.Conflicts.First(); + + //Resolve the conflict by taking the blob from branch + var blob = repo.Lookup(conflict.Theirs.Id); + //Add() does not remove conflict entries for the same path, so they must be explicitly removed first. + index.Remove(conflict.Ours.Path); + index.Add(blob, conflict.Ours.Path, Mode.NonExecutableFile); + + Assert.True(index.IsFullyMerged); + var tree = index.WriteToTree(); + + //Since we took the conflicted blob from the branch, the merged result should be the same as the branch. + Assert.Equal(branch.Tip.Tree.Id, tree.Id); + } + } + } + private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null) { Touch(repository.Info.WorkingDirectory, filename, content); - repository.Index.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/CleanFixture.cs b/LibGit2Sharp.Tests/CleanFixture.cs index 9613a2c96..39c7a6152 100644 --- a/LibGit2Sharp.Tests/CleanFixture.cs +++ b/LibGit2Sharp.Tests/CleanFixture.cs @@ -9,25 +9,26 @@ public class CleanFixture : BaseFixture [Fact] public void CanCleanWorkingDirectory() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Verify that there are the expected number of entries and untracked files - Assert.Equal(6, repo.Index.RetrieveStatus().Count()); - Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); + Assert.Equal(6, repo.RetrieveStatus().Count()); + Assert.Single(repo.RetrieveStatus().Untracked); repo.RemoveUntrackedFiles(); // Verify that there are the expected number of entries and 0 untracked files - Assert.Equal(5, repo.Index.RetrieveStatus().Count()); - Assert.Equal(0, repo.Index.RetrieveStatus().Untracked.Count()); + Assert.Equal(5, repo.RetrieveStatus().Count()); + Assert.Empty(repo.RetrieveStatus().Untracked); } } [Fact] public void CannotCleanABareRepository() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.RemoveUntrackedFiles()); } diff --git a/LibGit2Sharp.Tests/CloneFixture.cs b/LibGit2Sharp.Tests/CloneFixture.cs index a3b35d8b3..831f6779f 100644 --- a/LibGit2Sharp.Tests/CloneFixture.cs +++ b/LibGit2Sharp.Tests/CloneFixture.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using LibGit2Sharp.Handlers; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,8 +13,6 @@ public class CloneFixture : BaseFixture [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository")] - //[InlineData("git@github.com:libgit2/TestGitRepository")] public void CanClone(string url) { var scd = BuildSelfCleaningDirectory(); @@ -31,8 +30,56 @@ public void CanClone(string url) Assert.False(repo.Info.IsBare); Assert.True(File.Exists(Path.Combine(scd.RootedDirectoryPath, "master.txt"))); - Assert.Equal(repo.Head.Name, "master"); - Assert.Equal(repo.Head.Tip.Id.ToString(), "49322bb17d3acc9146f98c97d078513228bbf3c0"); + Assert.Equal("master", repo.Head.FriendlyName); + Assert.Equal("49322bb17d3acc9146f98c97d078513228bbf3c0", repo.Head.Tip.Id.ToString()); + } + } + + [Theory] + [InlineData("https://github.com/libgit2/TestGitRepository", 1)] + [InlineData("https://github.com/libgit2/TestGitRepository", 5)] + [InlineData("https://github.com/libgit2/TestGitRepository", 7)] + public void CanCloneShallow(string url, int depth) + { + var scd = BuildSelfCleaningDirectory(); + + var clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions + { + FetchOptions = + { + Depth = depth, + }, + }); + + using (var repo = new Repository(clonedRepoPath)) + { + var commitsFirstParentOnly = repo.Commits.QueryBy(new CommitFilter + { + FirstParentOnly = true, + }); + + Assert.Equal(depth, commitsFirstParentOnly.Count()); + Assert.Equal("49322bb17d3acc9146f98c97d078513228bbf3c0", repo.Head.Tip.Id.ToString()); + } + } + + [Theory] + [InlineData("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f")] + [InlineData("packed", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9")] + [InlineData("test", "e90810b8df3e80c413d903f631643c716887138d")] + public void CanCloneWithCheckoutBranchName(string branchName, string headTipId) + { + var scd = BuildSelfCleaningDirectory(); + + string clonedRepoPath = Repository.Clone(BareTestRepoPath, scd.DirectoryPath, new CloneOptions { BranchName = branchName }); + + using (var repo = new Repository(clonedRepoPath)) + { + var head = repo.Head; + + Assert.Equal(branchName, head.FriendlyName); + Assert.True(head.IsTracking); + Assert.Equal(headTipId, head.Tip.Sha); } } @@ -48,18 +95,18 @@ private void AssertLocalClone(string url, string path = null, bool isCloningAnEm Assert.NotEqual(originalRepo.Info.Path, clonedRepo.Info.Path); Assert.Equal(originalRepo.Head, clonedRepo.Head); - Assert.Equal(originalRepo.Branches.Count(), clonedRepo.Branches.Count(b => b.IsRemote)); + Assert.Equal(originalRepo.Branches.Count(), clonedRepo.Branches.Count(b => b.IsRemote && b.FriendlyName != "origin/HEAD")); Assert.Equal(isCloningAnEmptyRepository ? 0 : 1, clonedRepo.Branches.Count(b => !b.IsRemote)); Assert.Equal(originalRepo.Tags.Count(), clonedRepo.Tags.Count()); - Assert.Equal(1, clonedRepo.Network.Remotes.Count()); + Assert.Single(clonedRepo.Network.Remotes); } } [Fact] public void CanCloneALocalRepositoryFromALocalUri() { - var uri = new Uri(BareTestRepoPath); + var uri = new Uri($"file://{Path.GetFullPath(BareTestRepoPath)}"); AssertLocalClone(uri.AbsoluteUri, BareTestRepoPath); } @@ -72,7 +119,7 @@ public void CanCloneALocalRepositoryFromAStandardPath() [Fact] public void CanCloneALocalRepositoryFromANewlyCreatedTemporaryPath() { - var path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString().Substring(0, 8)); + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); SelfCleaningDirectory scd = BuildSelfCleaningDirectory(path); Repository.Init(scd.DirectoryPath); AssertLocalClone(scd.DirectoryPath, isCloningAnEmptyRepository: true); @@ -81,16 +128,14 @@ public void CanCloneALocalRepositoryFromANewlyCreatedTemporaryPath() [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository")] - //[InlineData("git@github.com:libgit2/TestGitRepository")] public void CanCloneBarely(string url) { var scd = BuildSelfCleaningDirectory(); string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions - { - IsBare = true - }); + { + IsBare = true + }); using (var repo = new Repository(clonedRepoPath)) { @@ -105,7 +150,7 @@ public void CanCloneBarely(string url) } [Theory] - [InlineData("git://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] public void WontCheckoutIfAskedNotTo(string url) { var scd = BuildSelfCleaningDirectory(); @@ -122,21 +167,31 @@ public void WontCheckoutIfAskedNotTo(string url) } [Theory] - [InlineData("git://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] public void CallsProgressCallbacks(string url) { bool transferWasCalled = false; + bool progressWasCalled = false; + bool updateTipsWasCalled = false; bool checkoutWasCalled = false; var scd = BuildSelfCleaningDirectory(); Repository.Clone(url, scd.DirectoryPath, new CloneOptions() { - OnTransferProgress = _ => { transferWasCalled = true; return true; }, + FetchOptions = + { + OnTransferProgress = _ => { transferWasCalled = true; return true; }, + OnProgress = progress => { progressWasCalled = true; return true; }, + OnUpdateTips = (name, oldId, newId) => { updateTipsWasCalled = true; return true; } + }, OnCheckoutProgress = (a, b, c) => checkoutWasCalled = true + }); Assert.True(transferWasCalled); + Assert.True(progressWasCalled); + Assert.True(updateTipsWasCalled); Assert.True(checkoutWasCalled); } @@ -151,7 +206,7 @@ public void CanCloneWithCredentials() string clonedRepoPath = Repository.Clone(Constants.PrivateRepoUrl, scd.DirectoryPath, new CloneOptions() { - CredentialsProvider = Constants.PrivateRepoCredentials + FetchOptions = { CredentialsProvider = Constants.PrivateRepoCredentials } }); @@ -167,37 +222,412 @@ public void CanCloneWithCredentials() } } + static Credentials CreateUsernamePasswordCredentials(string user, string pass, bool secure) + { + if (secure) + { + return new SecureUsernamePasswordCredentials + { + Username = user, + Password = Constants.StringToSecureString(pass), + }; + } + + return new UsernamePasswordCredentials + { + Username = user, + Password = pass, + }; + } + + //[Theory] + //[InlineData("https://libgit2@bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3", true)] + //[InlineData("https://libgit2@bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3", false)] + //public void CanCloneFromBBWithCredentials(string url, string user, string pass, bool secure) + //{ + // var scd = BuildSelfCleaningDirectory(); + + // string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions() + // { + // CredentialsProvider = (_url, _user, _cred) => CreateUsernamePasswordCredentials(user, pass, secure) + // }); + + // using (var repo = new Repository(clonedRepoPath)) + // { + // string dir = repo.Info.Path; + // Assert.True(Path.IsPathRooted(dir)); + // Assert.True(Directory.Exists(dir)); + + // Assert.NotNull(repo.Info.WorkingDirectory); + // Assert.Equal(Path.Combine(scd.RootedDirectoryPath, ".git" + Path.DirectorySeparatorChar), repo.Info.Path); + // Assert.False(repo.Info.IsBare); + // } + //} + + [SkippableTheory] + [InlineData("https://github.com/libgit2/TestGitRepository.git", "github.com", typeof(CertificateX509))] + //[InlineData("git@github.com:libgit2/TestGitRepository.git", "github.com", typeof(CertificateSsh))] + public void CanInspectCertificateOnClone(string url, string hostname, Type certType) + { + var scd = BuildSelfCleaningDirectory(); + + InconclusiveIf( + () => + certType == typeof(CertificateSsh) && !GlobalSettings.Version.Features.HasFlag(BuiltInFeatures.Ssh), + "SSH not supported"); + + bool wasCalled = false; + bool checksHappy = false; + + var options = new CloneOptions + { + FetchOptions = + { + CertificateCheck = (cert, valid, host) => + { + wasCalled = true; + + Assert.Equal(hostname, host); + Assert.Equal(certType, cert.GetType()); + + if (certType == typeof(CertificateX509)) + { + Assert.True(valid); + var x509 = ((CertificateX509)cert).Certificate; + // we get a string with the different fields instead of a structure, so... + Assert.Contains("CN=github.com", x509.Subject); + checksHappy = true; + return false; + } + + if (certType == typeof(CertificateSsh)) + { + var hostkey = (CertificateSsh)cert; + Assert.True(hostkey.HasMD5); + /* + * Once you've connected and thus your ssh has stored the hostkey, + * you can get the hostkey for a host with + * + * ssh-keygen -F github.com -l | tail -n 1 | cut -d ' ' -f 2 | tr -d ':' + * + * though GitHub's hostkey won't change anytime soon. + */ + Assert.Equal("1627aca576282d36631b564debdfa648", + BitConverter.ToString(hostkey.HashMD5).ToLower().Replace("-", "")); + checksHappy = true; + return false; + } + + return false; + } + } + }; + + Assert.Throws(() => + Repository.Clone(url, scd.DirectoryPath, options) + ); + + Assert.True(wasCalled); + Assert.True(checksHappy); + } + [Theory] - [InlineData("https://libgit2@bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")] - public void CanCloneFromBBWithCredentials(string url, string user, string pass) + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CloningWithoutWorkdirPathThrows(string url) + { + Assert.Throws(() => Repository.Clone(url, null)); + } + + [Fact] + public void CloningWithoutUrlThrows() { var scd = BuildSelfCleaningDirectory(); - string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions() { - CredentialsProvider = (_url, _user, _cred) => new UsernamePasswordCredentials { - Username = user, - Password = pass, + Assert.Throws(() => Repository.Clone(null, scd.DirectoryPath)); + } + + /// + /// Private helper to record the callbacks that were called as part of a clone. + /// + private class CloneCallbackInfo + { + /// + /// Was checkout progress called. + /// + public bool CheckoutProgressCalled { get; set; } + + /// + /// The reported remote URL. + /// + public string RemoteUrl { get; set; } + + /// + /// Was remote ref update called. + /// + public bool RemoteRefUpdateCalled { get; set; } + + /// + /// Was the transition callback called when starting + /// work on this repository. + /// + public bool StartingWorkInRepositoryCalled { get; set; } + + /// + /// Was the transition callback called when finishing + /// work on this repository. + /// + public bool FinishedWorkInRepositoryCalled { get; set; } + + /// + /// The reported recursion depth. + /// + public int RecursionDepth { get; set; } + } + + [Fact] + public void CanRecursivelyCloneSubmodules() + { + var uri = new Uri($"file://{Path.GetFullPath(SandboxSubmoduleSmallTestRepo())}"); + var scd = BuildSelfCleaningDirectory(); + string relativeSubmodulePath = "submodule_target_wd"; + + // Construct the expected URL the submodule will clone from. + string expectedSubmoduleUrl = Path.Combine(Path.GetDirectoryName(uri.AbsolutePath), relativeSubmodulePath); + expectedSubmoduleUrl = expectedSubmoduleUrl.Replace('\\', '/'); + + Dictionary callbacks = new Dictionary(); + + CloneCallbackInfo currentEntry = null; + bool unexpectedOrderOfCallbacks = false; + + CheckoutProgressHandler checkoutProgressHandler = (x, y, z) => + { + if (currentEntry != null) + { + currentEntry.CheckoutProgressCalled = true; + } + else + { + // Should not be called if there is not a current + // callbackInfo entry. + unexpectedOrderOfCallbacks = true; + } + }; + + UpdateTipsHandler remoteRefUpdated = (x, y, z) => + { + if (currentEntry != null) + { + currentEntry.RemoteRefUpdateCalled = true; + } + else + { + // Should not be called if there is not a current + // callbackInfo entry. + unexpectedOrderOfCallbacks = true; } + + return true; + }; + + RepositoryOperationStarting repositoryOperationStarting = (x) => + { + if (currentEntry != null) + { + // Should not be called if there is a current + // callbackInfo entry. + unexpectedOrderOfCallbacks = true; + } + + currentEntry = new CloneCallbackInfo(); + currentEntry.StartingWorkInRepositoryCalled = true; + currentEntry.RecursionDepth = x.RecursionDepth; + currentEntry.RemoteUrl = x.RemoteUrl; + callbacks.Add(x.RepositoryPath, currentEntry); + + return true; + }; + + RepositoryOperationCompleted repositoryOperationCompleted = (x) => + { + if (currentEntry != null) + { + currentEntry.FinishedWorkInRepositoryCalled = true; + currentEntry = null; + } + else + { + // Should not be called if there is not a current + // callbackInfo entry. + unexpectedOrderOfCallbacks = true; + } + }; + + CloneOptions options = new CloneOptions() + { + RecurseSubmodules = true, + OnCheckoutProgress = checkoutProgressHandler, + FetchOptions = + { + OnUpdateTips = remoteRefUpdated, + RepositoryOperationStarting = repositoryOperationStarting, + RepositoryOperationCompleted = repositoryOperationCompleted + } + }; + + string clonedRepoPath = Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); + string workDirPath; + + using (Repository repo = new Repository(clonedRepoPath)) + { + workDirPath = repo.Info.WorkingDirectory.TrimEnd(new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }); + } + + // Verification: + // Verify that no callbacks were called in an unexpected order. + Assert.False(unexpectedOrderOfCallbacks); + + Dictionary expectedCallbackInfo = new Dictionary(); + expectedCallbackInfo.Add(workDirPath, new CloneCallbackInfo() + { + RecursionDepth = 0, + RemoteUrl = uri.AbsolutePath, + StartingWorkInRepositoryCalled = true, + FinishedWorkInRepositoryCalled = true, + CheckoutProgressCalled = true, + RemoteRefUpdateCalled = true, }); - using (var repo = new Repository(clonedRepoPath)) + expectedCallbackInfo.Add(Path.Combine(workDirPath, relativeSubmodulePath), new CloneCallbackInfo() { - string dir = repo.Info.Path; - Assert.True(Path.IsPathRooted(dir)); - Assert.True(Directory.Exists(dir)); + RecursionDepth = 1, + RemoteUrl = expectedSubmoduleUrl, + StartingWorkInRepositoryCalled = true, + FinishedWorkInRepositoryCalled = true, + CheckoutProgressCalled = true, + RemoteRefUpdateCalled = true, + }); - Assert.NotNull(repo.Info.WorkingDirectory); - Assert.Equal(Path.Combine(scd.RootedDirectoryPath, ".git" + Path.DirectorySeparatorChar), repo.Info.Path); - Assert.False(repo.Info.IsBare); + // Callbacks for each expected repository that is cloned + foreach (KeyValuePair kvp in expectedCallbackInfo) + { + CloneCallbackInfo entry = null; + Assert.True(callbacks.TryGetValue(kvp.Key, out entry), string.Format("{0} was not found in callbacks.", kvp.Key)); + + Assert.Equal(kvp.Value.RemoteUrl, entry.RemoteUrl); + Assert.Equal(kvp.Value.RecursionDepth, entry.RecursionDepth); + Assert.Equal(kvp.Value.StartingWorkInRepositoryCalled, entry.StartingWorkInRepositoryCalled); + Assert.Equal(kvp.Value.FinishedWorkInRepositoryCalled, entry.FinishedWorkInRepositoryCalled); + Assert.Equal(kvp.Value.CheckoutProgressCalled, entry.CheckoutProgressCalled); + Assert.Equal(kvp.Value.RemoteRefUpdateCalled, entry.RemoteRefUpdateCalled); + } + + // Verify the state of the submodule + using (Repository repo = new Repository(clonedRepoPath)) + { + var sm = repo.Submodules[relativeSubmodulePath]; + Assert.True(sm.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir | + SubmoduleStatus.InConfig | + SubmoduleStatus.InIndex | + SubmoduleStatus.InHead)); + + Assert.NotNull(sm.HeadCommitId); + Assert.Equal("480095882d281ed676fe5b863569520e54a7d5c0", sm.HeadCommitId.Sha); + + Assert.False(repo.RetrieveStatus().IsDirty); } } [Fact] - public void CloningAnUrlWithoutPathThrows() + public void CanCancelRecursiveClone() { + var uri = new Uri($"file://{Path.GetFullPath(SandboxSubmoduleSmallTestRepo())}"); var scd = BuildSelfCleaningDirectory(); + string relativeSubmodulePath = "submodule_target_wd"; + + int cancelDepth = 0; + + RepositoryOperationStarting repositoryOperationStarting = (x) => + { + return !(x.RecursionDepth >= cancelDepth); + }; + + CloneOptions options = new CloneOptions() + { + RecurseSubmodules = true, + FetchOptions = { RepositoryOperationStarting = repositoryOperationStarting } + }; + + Assert.Throws(() => + Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options)); + + // Cancel after super repository is cloned, but before submodule is cloned. + cancelDepth = 1; + + string clonedRepoPath = null; + + try + { + Repository.Clone(uri.AbsolutePath, scd.DirectoryPath, options); + } + catch (RecurseSubmodulesException ex) + { + Assert.NotNull(ex.InnerException); + Assert.Equal(typeof(UserCancelledException), ex.InnerException.GetType()); + clonedRepoPath = ex.InitialRepositoryPath; + } + + // Verify that the submodule was not initialized. + using (Repository repo = new Repository(clonedRepoPath)) + { + var submoduleStatus = repo.Submodules[relativeSubmodulePath].RetrieveStatus(); + Assert.Equal(SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.WorkDirUninitialized, + submoduleStatus); + + } + } + + [Fact] + public void CannotCloneWithForbiddenCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + const string knownHeader = "User-Agent: mygit-201"; + var cloneOptions = new CloneOptions(); + cloneOptions.FetchOptions.CustomHeaders = new string[] { knownHeader }; + + Assert.Throws(() => Repository.Clone(url, scd.DirectoryPath, cloneOptions)); + } + + [Fact] + public void CannotCloneWithMalformedCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + const string knownHeader = "hello world"; + var cloneOptions = new CloneOptions(); + cloneOptions.FetchOptions.CustomHeaders = new string[] { knownHeader }; + + Assert.Throws(() => Repository.Clone(url, scd.DirectoryPath, cloneOptions)); + } + + [Fact] + public void CanCloneWithCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + const string knownHeader = "X-Hello: world"; + var cloneOptions = new CloneOptions(); + cloneOptions.FetchOptions.CustomHeaders = new string[] { knownHeader }; - Assert.Throws(() => Repository.Clone("http://github.com", scd.DirectoryPath)); + var clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, cloneOptions); + Assert.True(Directory.Exists(clonedRepoPath)); } } } diff --git a/LibGit2Sharp.Tests/CommitAncestorFixture.cs b/LibGit2Sharp.Tests/CommitAncestorFixture.cs index fe83c1117..c752f7415 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -35,12 +35,13 @@ public class CommitAncestorFixture : BaseFixture [InlineData(null, "be3563a", "-")] public void FindCommonAncestorForTwoCommits(string result, string sha1, string sha2) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var first = sha1 == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha1); var second = sha2 == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha2); - Commit ancestor = repo.Commits.FindMergeBase(first, second); + Commit ancestor = repo.ObjectDatabase.FindMergeBase(first, second); if (result == null) { @@ -65,11 +66,12 @@ public void FindCommonAncestorForTwoCommits(string result, string sha1, string s [InlineData(null, new[] { "4c062a6", "-" }, MergeBaseFindingStrategy.Standard)] public void FindCommonAncestorForCommitsAsEnumerable(string result, string[] shas, MergeBaseFindingStrategy strategy) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); - Commit ancestor = repo.Commits.FindMergeBase(commits, strategy); + Commit ancestor = repo.ObjectDatabase.FindMergeBase(commits, strategy); if (result == null) { @@ -88,12 +90,13 @@ public void FindCommonAncestorForCommitsAsEnumerable(string result, string[] sha [InlineData("0000000", "4c062a6")] public void FindCommonAncestorForTwoCommitsThrows(string sha1, string sha2) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var first = repo.Lookup(sha1); var second = repo.Lookup(sha2); - Assert.Throws(() => repo.Commits.FindMergeBase(first, second)); + Assert.Throws(() => repo.ObjectDatabase.FindMergeBase(first, second)); } } @@ -104,11 +107,12 @@ public void FindCommonAncestorForTwoCommitsThrows(string sha1, string sha2) [InlineData(new[] { "4c062a6", "be3563a", "000000" }, MergeBaseFindingStrategy.Standard)] public void FindCommonAncestorForCommitsAsEnumerableThrows(string[] shas, MergeBaseFindingStrategy strategy) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); - Assert.Throws(() => repo.Commits.FindMergeBase(commits, strategy)); + Assert.Throws(() => repo.ObjectDatabase.FindMergeBase(commits, strategy)); } } diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index e372fd7d2..e99ca918f 100644 --- a/LibGit2Sharp.Tests/CommitFixture.cs +++ b/LibGit2Sharp.Tests/CommitFixture.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using LibGit2Sharp.Core; +using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -16,7 +16,8 @@ public class CommitFixture : BaseFixture [Fact] public void CanCountCommits() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(7, repo.Commits.Count()); } @@ -25,18 +26,18 @@ public void CanCountCommits() [Fact] public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Hard reset and then remove untracked files repo.Reset(ResetMode.Hard); repo.RemoveUntrackedFiles(); - repo.Checkout("test"); + Commands.Checkout(repo, "test"); Assert.Equal(2, repo.Commits.Count()); Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", repo.Commits.First().Id.Sha); - repo.Checkout("master"); + Commands.Checkout(repo, "master"); Assert.Equal(9, repo.Commits.Count()); Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", repo.Commits.First().Id.Sha); } @@ -46,7 +47,8 @@ public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch() public void CanEnumerateCommits() { int count = 0; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { foreach (Commit commit in repo.Commits) { @@ -60,13 +62,13 @@ public void CanEnumerateCommits() [Fact] public void CanEnumerateCommitsInDetachedHeadState() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { ObjectId parentOfHead = repo.Head.Tip.Parents.First().Id; repo.Refs.Add("HEAD", parentOfHead.Sha, true); - Assert.Equal(true, repo.Info.IsHeadDetached); + Assert.True(repo.Info.IsHeadDetached); Assert.Equal(6, repo.Commits.Count()); } @@ -75,7 +77,8 @@ public void CanEnumerateCommitsInDetachedHeadState() [Fact] public void DefaultOrderingWhenEnumeratingCommitsIsTimeBased() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(CommitSortStrategies.Time, repo.Commits.SortedBy); } @@ -85,9 +88,10 @@ public void DefaultOrderingWhenEnumeratingCommitsIsTimeBased() public void CanEnumerateCommitsFromSha() { int count = 0; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) + foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) { Assert.NotNull(commit); count++; @@ -99,34 +103,36 @@ public void CanEnumerateCommitsFromSha() [Fact] public void QueryingTheCommitHistoryWithUnknownShaOrInvalidEntryPointThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = Constants.UnknownSha }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = "refs/heads/deadbeef" }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = null }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = Constants.UnknownSha }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "refs/heads/deadbeef" }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = null }).Count()); } } [Fact] public void QueryingTheCommitHistoryFromACorruptedReferenceThrows() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { CreateCorruptedDeadBeefHead(repo.Info.Path); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = repo.Branches["deadbeef"] }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs["refs/heads/deadbeef"] }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches["deadbeef"] }).Count()); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs["refs/heads/deadbeef"] }).Count()); } } [Fact] public void QueryingTheCommitHistoryWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = string.Empty })); - Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { Since = null })); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = string.Empty })); + Assert.Throws(() => repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = null })); Assert.Throws(() => repo.Commits.QueryBy(default(CommitFilter))); } } @@ -138,16 +144,17 @@ public void CanEnumerateCommitsWithReverseTimeSorting() reversedShas.Reverse(); int count = 0; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter - { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", - SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse - })) + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse + })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(reversedShas[count])); + Assert.StartsWith(reversedShas[count], commit.Sha); count++; } } @@ -157,13 +164,14 @@ public void CanEnumerateCommitsWithReverseTimeSorting() [Fact] public void CanEnumerateCommitsWithReverseTopoSorting() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { List commits = repo.Commits.QueryBy(new CommitFilter - { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", - SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse - }).ToList(); + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse + }).ToList(); foreach (Commit commit in commits) { Assert.NotNull(commit); @@ -180,7 +188,7 @@ public void CanEnumerateCommitsWithReverseTopoSorting() public void CanSimplifyByFirstParent() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Head, FirstParentOnly = true }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head, FirstParentOnly = true }, new[] { "4c062a6", "be3563a", "9fd738e", @@ -191,9 +199,10 @@ public void CanSimplifyByFirstParent() [Fact] public void CanGetParentsCount() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(1, repo.Commits.First().Parents.Count()); + Assert.Single(repo.Commits.First().Parents); } } @@ -201,16 +210,17 @@ public void CanGetParentsCount() public void CanEnumerateCommitsWithTimeSorting() { int count = 0; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter - { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", - SortBy = CommitSortStrategies.Time - })) + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time + })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(expectedShas[count])); + Assert.StartsWith(expectedShas[count], commit.Sha); count++; } } @@ -220,13 +230,14 @@ public void CanEnumerateCommitsWithTimeSorting() [Fact] public void CanEnumerateCommitsWithTopoSorting() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { List commits = repo.Commits.QueryBy(new CommitFilter - { - Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", - SortBy = CommitSortStrategies.Topological - }).ToList(); + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Topological + }).ToList(); foreach (Commit commit in commits) { Assert.NotNull(commit); @@ -243,7 +254,7 @@ public void CanEnumerateCommitsWithTopoSorting() public void CanEnumerateFromHead() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "4c062a6", "be3563a", "c47800c", "9fd738e", @@ -254,7 +265,7 @@ public void CanEnumerateFromHead() [Fact] public void CanEnumerateFromDetachedHead() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repoClone = new Repository(path)) { // Hard reset and then remove untracked files @@ -262,10 +273,10 @@ public void CanEnumerateFromDetachedHead() repoClone.RemoveUntrackedFiles(); string headSha = repoClone.Head.Tip.Sha; - repoClone.Checkout(headSha); + Commands.Checkout(repoClone, headSha); AssertEnumerationOfCommitsInRepo(repoClone, - repo => new CommitFilter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "32eab9c", "592d3c8", "4c062a6", @@ -279,7 +290,7 @@ public void CanEnumerateFromDetachedHead() public void CanEnumerateUsingTwoHeadsAsBoundaries() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "HEAD", Until = "refs/heads/br2" }, + repo => new CommitFilter { IncludeReachableFrom = "HEAD", ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -288,7 +299,7 @@ public void CanEnumerateUsingTwoHeadsAsBoundaries() public void CanEnumerateUsingImplicitHeadAsSinceBoundary() { AssertEnumerationOfCommits( - repo => new CommitFilter { Until = "refs/heads/br2" }, + repo => new CommitFilter { ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -297,7 +308,7 @@ public void CanEnumerateUsingImplicitHeadAsSinceBoundary() public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "a4a7dce", Until = "4a202b3" }, + repo => new CommitFilter { IncludeReachableFrom = "a4a7dce", ExcludeReachableFrom = "4a202b3" }, new[] { "a4a7dce", "c47800c", "9fd738e" } ); } @@ -306,7 +317,7 @@ public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() public void CanEnumerateCommitsFromTwoHeads() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = new[] { "refs/heads/br2", "refs/heads/master" } }, + repo => new CommitFilter { IncludeReachableFrom = new[] { "refs/heads/br2", "refs/heads/master" } }, new[] { "4c062a6", "a4a7dce", "be3563a", "c47800c", @@ -318,9 +329,12 @@ public void CanEnumerateCommitsFromTwoHeads() public void CanEnumerateCommitsFromMixedStartingPoints() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = new object[] { repo.Branches["br2"], + repo => new CommitFilter + { + IncludeReachableFrom = new object[] { repo.Branches["br2"], "refs/heads/master", - new ObjectId("e90810b8df3e80c413d903f631643c716887138d") } }, + new ObjectId("e90810b8df3e80c413d903f631643c716887138d") } + }, new[] { "4c062a6", "e90810b", "6dcf9bf", "a4a7dce", @@ -333,7 +347,7 @@ public void CanEnumerateCommitsFromMixedStartingPoints() public void CanEnumerateCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Refs.FromGlob("refs/heads/*") }, + repo => new CommitFilter { IncludeReachableFrom = repo.Refs.FromGlob("refs/heads/*") }, new[] { "4c062a6", "e90810b", "6dcf9bf", "a4a7dce", "be3563a", "c47800c", "9fd738e", "4a202b3", "41bc8c6", "5001298", "5b5b025", "8496071" @@ -344,7 +358,7 @@ public void CanEnumerateCommitsUsingGlob() public void CanHideCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = "refs/heads/packed-test", Until = repo.Refs.FromGlob("*/packed") }, + repo => new CommitFilter { IncludeReachableFrom = "refs/heads/packed-test", ExcludeReachableFrom = repo.Refs.FromGlob("*/packed") }, new[] { "4a202b3", "5b5b025", "8496071" @@ -363,10 +377,10 @@ public void CanEnumerateCommitsFromATagAnnotation() CanEnumerateCommitsFromATag(t => t.Annotation); } - private static void CanEnumerateCommitsFromATag(Func transformer) + private void CanEnumerateCommitsFromATag(Func transformer) { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = transformer(repo.Tags["test"]) }, + repo => new CommitFilter { IncludeReachableFrom = transformer(repo.Tags["test"]) }, new[] { "e90810b", "6dcf9bf", } ); } @@ -376,9 +390,9 @@ public void CanEnumerateAllCommits() { AssertEnumerationOfCommits( repo => new CommitFilter - { - Since = repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal), - }, + { + IncludeReachableFrom = repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal), + }, new[] { "44d5d18", "bb65291", "532740a", "503a16f", "3dfd6fd", @@ -392,14 +406,14 @@ public void CanEnumerateAllCommits() public void CanEnumerateCommitsFromATagWhichPointsToABlob() { AssertEnumerationOfCommits( - repo => new CommitFilter { Since = repo.Tags["point_to_blob"] }, - new string[] { }); + repo => new CommitFilter { IncludeReachableFrom = repo.Tags["point_to_blob"] }, + Array.Empty()); } [Fact] public void CanEnumerateCommitsFromATagWhichPointsToATree() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { string headTreeSha = repo.Head.Tip.Tree.Sha; @@ -407,14 +421,15 @@ public void CanEnumerateCommitsFromATagWhichPointsToATree() Tag tag = repo.ApplyTag("point_to_tree", headTreeSha); AssertEnumerationOfCommitsInRepo(repo, - r => new CommitFilter { Since = tag }, - new string[] { }); + r => new CommitFilter { IncludeReachableFrom = tag }, + Array.Empty()); } } - private static void AssertEnumerationOfCommits(Func filterBuilder, IEnumerable abbrevIds) + private void AssertEnumerationOfCommits(Func filterBuilder, IEnumerable abbrevIds) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { AssertEnumerationOfCommitsInRepo(repo, filterBuilder, abbrevIds); } @@ -432,7 +447,8 @@ private static void AssertEnumerationOfCommitsInRepo(IRepository repo, Func(sha); Assert.Equal("testing\n", commit.Message); @@ -444,7 +460,8 @@ public void CanLookupCommitGeneric() [Fact] public void CanReadCommitData() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject obj = repo.Lookup(sha); Assert.NotNull(obj); @@ -459,23 +476,24 @@ public void CanReadCommitData() Assert.NotNull(commit.Author); Assert.Equal("Scott Chacon", commit.Author.Name); Assert.Equal("schacon@gmail.com", commit.Author.Email); - Assert.Equal(1273360386, commit.Author.When.ToSecondsSinceEpoch()); + Assert.Equal(1273360386, commit.Author.When.ToUnixTimeSeconds()); Assert.NotNull(commit.Committer); Assert.Equal("Scott Chacon", commit.Committer.Name); Assert.Equal("schacon@gmail.com", commit.Committer.Email); - Assert.Equal(1273360386, commit.Committer.When.ToSecondsSinceEpoch()); + Assert.Equal(1273360386, commit.Committer.When.ToUnixTimeSeconds()); Assert.Equal("181037049a54a1eb5fab404658a3a250b44335d7", commit.Tree.Sha); - Assert.Equal(0, commit.Parents.Count()); + Assert.Empty(commit.Parents); } } [Fact] public void CanReadCommitWithMultipleParents() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("a4a7dce85cf63874e984719f4fdd239f5145052f"); Assert.Equal(2, commit.Parents.Count()); @@ -485,7 +503,8 @@ public void CanReadCommitWithMultipleParents() [Fact] public void CanDirectlyAccessABlobOfTheCommit() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4c062a6"); @@ -499,7 +518,8 @@ public void CanDirectlyAccessABlobOfTheCommit() [Fact] public void CanDirectlyAccessATreeOfTheCommit() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4c062a6"); @@ -511,7 +531,8 @@ public void CanDirectlyAccessATreeOfTheCommit() [Fact] public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4c062a6"); @@ -523,37 +544,38 @@ public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() public void CanCommitWithSignatureFromConfig() { string repoPath = InitNewRepository(); - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - using (var repo = new Repository(repoPath, options)) + using (var repo = new Repository(repoPath)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); Assert.True(Directory.Exists(dir)); const string relativeFilepath = "new.txt"; string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.Null(repo.Head[relativeFilepath]); - Commit commit = repo.Commit("Initial egotistic commit"); + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + Commit commit = repo.Commit("Initial egotistic commit", signature, signature); AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); - AssertCommitSignaturesAre(commit, Constants.Signature); + AssertCommitIdentitiesAre(commit, Constants.Identity); } } [Fact] public void CommitParentsAreMergeHeads() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard, "c47800"); @@ -569,14 +591,14 @@ public void CommitParentsAreMergeHeads() Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); Assert.Equal(2, newMergedCommit.Parents.Count()); - Assert.Equal(newMergedCommit.Parents.First().Sha, "c47800c7266a2be04c571c04d5a6614691ea99bd"); - Assert.Equal(newMergedCommit.Parents.Skip(1).First().Sha, "9fd738e8f7967c078dceed8190330fc8648ee56a"); + Assert.Equal("c47800c7266a2be04c571c04d5a6614691ea99bd", newMergedCommit.Parents.First().Sha); + Assert.Equal("9fd738e8f7967c078dceed8190330fc8648ee56a", newMergedCommit.Parents.Skip(1).First().Sha); // Assert reflog entry is created var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); Assert.Equal(repo.Head.Tip.Id, reflogEntry.To); - Assert.NotNull(reflogEntry.Commiter.Email); - Assert.NotNull(reflogEntry.Commiter.Name); + Assert.NotNull(reflogEntry.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); Assert.Equal(string.Format("commit (merge): {0}", newMergedCommit.MessageShort), reflogEntry.Message); } } @@ -594,7 +616,7 @@ public void CommitCleansUpMergeMetadata() const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "this is a new file"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); string mergeHeadPath = Touch(repo.Info.Path, "MERGE_HEAD", "abcdefabcdefabcdefabcdefabcdefabcdefabcd"); string mergeMsgPath = Touch(repo.Info.Path, "MERGE_MSG", "This is a dummy merge.\n"); @@ -621,7 +643,9 @@ public void CanCommitALittleBit() { string repoPath = InitNewRepository(); - using (var repo = new Repository(repoPath)) + var identity = Constants.Identity; + + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = identity })) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); @@ -629,9 +653,9 @@ public void CanCommitALittleBit() const string relativeFilepath = "new.txt"; string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.Null(repo.Head[relativeFilepath]); @@ -640,29 +664,41 @@ public void CanCommitALittleBit() const string shortMessage = "Initial egotistic commit"; const string commitMessage = shortMessage + "\n\nOnly the coolest commits from us"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit commit = repo.Commit(commitMessage, author, author); AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); - Assert.Equal(0, commit.Parents.Count()); + Assert.Empty(commit.Parents); Assert.False(repo.Info.IsHeadUnborn); // Assert a reflog entry is created on HEAD - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); var reflogEntry = repo.Refs.Log("HEAD").First(); - Assert.Equal(author, reflogEntry.Commiter); + + Assert.Equal(identity.Name, reflogEntry.Committer.Name); + Assert.Equal(identity.Email, reflogEntry.Committer.Email); + + // When verifying the timestamp range, give a little more room on the range. + // Git or file system datetime truncation seems to cause these stamps to jump up to a second earlier + // than we expect. See https://github.com/libgit2/libgit2sharp/issues/1764 + var low = before - TimeSpan.FromSeconds(1); + var high = DateTimeOffset.Now.TruncateMilliseconds() + TimeSpan.FromSeconds(1); + Assert.InRange(reflogEntry.Committer.When, low, high); + Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); Assert.Equal(string.Format("commit (initial): {0}", shortMessage), reflogEntry.Message); // Assert a reflog entry is created on HEAD target var targetCanonicalName = repo.Refs.Head.TargetIdentifier; - Assert.Equal(1, repo.Refs.Log(targetCanonicalName).Count()); + Assert.Single(repo.Refs.Log(targetCanonicalName)); Assert.Equal(commit.Id, repo.Refs.Log(targetCanonicalName).First().To); File.WriteAllText(filePath, "nulltoken commits!\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author2 = new Signature(author.Name, author.Email, author.When.AddSeconds(5)); Commit commit2 = repo.Commit("Are you trying to fork me?", author2, author2); @@ -670,7 +706,7 @@ public void CanCommitALittleBit() AssertBlobContent(repo.Head[relativeFilepath], "nulltoken commits!\n"); AssertBlobContent(commit2[relativeFilepath], "nulltoken commits!\n"); - Assert.Equal(1, commit2.Parents.Count()); + Assert.Single(commit2.Parents); Assert.Equal(commit.Id, commit2.Parents.First().Id); // Assert the reflog is shifted @@ -678,19 +714,19 @@ public void CanCommitALittleBit() Assert.Equal(reflogEntry.To, repo.Refs.Log("HEAD").First().From); Branch firstCommitBranch = repo.CreateBranch("davidfowl-rules", commit); - repo.Checkout(firstCommitBranch); + Commands.Checkout(repo, firstCommitBranch); File.WriteAllText(filePath, "davidfowl commits!\n"); var author3 = new Signature("David Fowler", "david.fowler@microsoft.com", author.When.AddSeconds(2)); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Commit commit3 = repo.Commit("I'm going to branch you backwards in time!", author3, author3); AssertBlobContent(repo.Head[relativeFilepath], "davidfowl commits!\n"); AssertBlobContent(commit3[relativeFilepath], "davidfowl commits!\n"); - Assert.Equal(1, commit3.Parents.Count()); + Assert.Single(commit3.Parents); Assert.Equal(commit.Id, commit3.Parents.First().Id); AssertBlobContent(firstCommitBranch[relativeFilepath], "nulltoken\n"); @@ -709,7 +745,7 @@ private static void AddCommitToRepo(string path) { const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100")); repo.Commit("Initial commit", author, author); @@ -745,17 +781,17 @@ public void CanAmendARootCommit() using (var repo = new Repository(repoPath)) { - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); Commit originalCommit = repo.Head.Tip; - Assert.Equal(0, originalCommit.Parents.Count()); + Assert.Empty(originalCommit.Parents); CreateAndStageANewFile(repo); Commit amendedCommit = repo.Commit("I'm rewriting the history!", Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true }); - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); AssertCommitHasBeenAmended(repo, amendedCommit, originalCommit); } @@ -764,8 +800,8 @@ public void CanAmendARootCommit() [Fact] public void CanAmendACommitWithMoreThanOneParent() { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { var mergedCommit = repo.Lookup("be3563a"); Assert.NotNull(mergedCommit); @@ -776,24 +812,26 @@ public void CanAmendACommitWithMoreThanOneParent() CreateAndStageANewFile(repo); const string commitMessage = "I'm rewriting the history!"; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit amendedCommit = repo.Commit(commitMessage, Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true }); AssertCommitHasBeenAmended(repo, amendedCommit, mergedCommit); AssertRefLogEntry(repo, "HEAD", - amendedCommit.Id, string.Format("commit (amend): {0}", commitMessage), mergedCommit.Id, - amendedCommit.Committer); + amendedCommit.Id, + Constants.Identity, before); } } private static void CreateAndStageANewFile(IRepository repo) { - string relativeFilepath = string.Format("new-file-{0}.txt", Guid.NewGuid()); + string relativeFilepath = string.Format("new-file-{0}.txt", Path.GetRandomFileName()); Touch(repo.Info.WorkingDirectory, relativeFilepath, "brand new content\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); } private static void AssertCommitHasBeenAmended(IRepository repo, Commit amendedCommit, Commit originalCommit) @@ -820,27 +858,27 @@ public void CanNotAmendAnEmptyRepository() [Fact] public void CanRetrieveChildrenOfASpecificCommit() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string parentSha = "5b5b025afb0b4c913b4c338a42934a3863bf3644"; var filter = new CommitFilter - { - /* Revwalk from all the refs (git log --all) ... */ - Since = repo.Refs, + { + /* Revwalk from all the refs (git log --all) ... */ + IncludeReachableFrom = repo.Refs, - /* ... and stop when the parent is reached */ - Until = parentSha - }; + /* ... and stop when the parent is reached */ + ExcludeReachableFrom = parentSha + }; var commits = repo.Commits.QueryBy(filter); var children = from c in commits - from p in c.Parents - let pId = p.Id - where pId.Sha == parentSha - select c; + from p in c.Parents + let pId = p.Id + where pId.Sha == parentSha + select c; var expectedChildren = new[] { "c47800c7266a2be04c571c04d5a6614691ea99bd", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }; @@ -852,13 +890,13 @@ from p in c.Parents [Fact] public void CanCorrectlyDistinguishAuthorFromCommitter() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var author = new Signature("Wilbert van Dolleweerd", "getit@xs4all.nl", - Epoch.ToDateTimeOffset(1244187936, 120)); + DateTimeOffset.FromUnixTimeSeconds(1244187936).ToOffset(TimeSpan.FromMinutes(120))); var committer = new Signature("Henk Westhuis", "Henk_Westhuis@hotmail.com", - Epoch.ToDateTimeOffset(1244286496, 120)); + DateTimeOffset.FromUnixTimeSeconds(1244286496).ToOffset(TimeSpan.FromMinutes(120))); Commit c = repo.Commit("I can haz an author and a committer!", author, committer); @@ -882,17 +920,17 @@ public void CanCommitOnOrphanedBranch() const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); - Assert.Equal(1, repo.Head.Commits.Count()); + Assert.Single(repo.Head.Commits); } } [Fact] public void CanNotCommitAnEmptyCommit() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -905,7 +943,7 @@ public void CanNotCommitAnEmptyCommit() [Fact] public void CanCommitAnEmptyCommitWhenForced() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -919,7 +957,7 @@ public void CanCommitAnEmptyCommitWhenForced() [Fact] public void CanNotAmendAnEmptyCommit() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -936,7 +974,7 @@ public void CanNotAmendAnEmptyCommit() [Fact] public void CanAmendAnEmptyCommitWhenForced() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -954,7 +992,7 @@ public void CanAmendAnEmptyCommitWhenForced() [Fact] public void CanCommitAnEmptyCommitWhenMerging() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -967,15 +1005,15 @@ public void CanCommitAnEmptyCommitWhenMerging() Commit newMergedCommit = repo.Commit("Merge commit", Constants.Signature, Constants.Signature); Assert.Equal(2, newMergedCommit.Parents.Count()); - Assert.Equal(newMergedCommit.Parents.First().Sha, "32eab9cb1f450b5fe7ab663462b77d7f4b703344"); - Assert.Equal(newMergedCommit.Parents.Skip(1).First().Sha, "f705abffe7015f2beacf2abe7a36583ebee3487e"); + Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newMergedCommit.Parents.First().Sha); + Assert.Equal("f705abffe7015f2beacf2abe7a36583ebee3487e", newMergedCommit.Parents.Skip(1).First().Sha); } } [Fact] public void CanAmendAnEmptyMergeCommit() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Reset(ResetMode.Hard); @@ -998,20 +1036,143 @@ public void CanNotAmendACommitInAWayThatWouldLeadTheNewCommitToBecomeEmpty() using (var repo = new Repository(repoPath)) { Touch(repo.Info.WorkingDirectory, "test.txt", "test\n"); - repo.Index.Stage("test.txt"); + Commands.Stage(repo, "test.txt"); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); Touch(repo.Info.WorkingDirectory, "new.txt", "content\n"); - repo.Index.Stage("new.txt"); + Commands.Stage(repo, "new.txt"); repo.Commit("One commit", Constants.Signature, Constants.Signature); - repo.Index.Remove("new.txt"); + Commands.Remove(repo, "new.txt"); Assert.Throws(() => repo.Commit("Oops", Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true })); } } + + [Fact] + public void CanPrettifyAMessage() + { + string input = "# Comment\nA line that will remain\n# And another character\n\n\n"; + string expected = "A line that will remain\n"; + + Assert.Equal(expected, Commit.PrettifyMessage(input, '#')); + Assert.Equal(expected, Commit.PrettifyMessage(input.Replace('#', ';'), ';')); + } + + private readonly string signedCommit = + "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n" + + "parent 8496071c1b46c854b31185ea97743be6a8774479\n" + + "author Ben Burkert 1358451456 -0800\n" + + "committer Ben Burkert 1358451456 -0800\n" + + "gpgsig -----BEGIN PGP SIGNATURE-----\n" + + " Version: GnuPG v1.4.12 (Darwin)\n" + + " \n" + + " iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n" + + " o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n" + + " JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n" + + " AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n" + + " SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n" + + " who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n" + + " 6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n" + + " cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n" + + " c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n" + + " ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n" + + " 7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n" + + " cpxtDQQMGYFpXK/71stq\n" + + " =ozeK\n" + + " -----END PGP SIGNATURE-----\n" + + "\n" + + "a simple commit which works\n"; + + private readonly string signatureData = + "-----BEGIN PGP SIGNATURE-----\n" + + "Version: GnuPG v1.4.12 (Darwin)\n" + + "\n" + + "iQIcBAABAgAGBQJQ+FMIAAoJEH+LfPdZDSs1e3EQAJMjhqjWF+WkGLHju7pTw2al\n" + + "o6IoMAhv0Z/LHlWhzBd9e7JeCnanRt12bAU7yvYp9+Z+z+dbwqLwDoFp8LVuigl8\n" + + "JGLcnwiUW3rSvhjdCp9irdb4+bhKUnKUzSdsR2CK4/hC0N2i/HOvMYX+BRsvqweq\n" + + "AsAkA6dAWh+gAfedrBUkCTGhlNYoetjdakWqlGL1TiKAefEZrtA1TpPkGn92vbLq\n" + + "SphFRUY9hVn1ZBWrT3hEpvAIcZag3rTOiRVT1X1flj8B2vGCEr3RrcwOIZikpdaW\n" + + "who/X3xh/DGbI2RbuxmmJpxxP/8dsVchRJJzBwG+yhwU/iN3MlV2c5D69tls/Dok\n" + + "6VbyU4lm/ae0y3yR83D9dUlkycOnmmlBAHKIZ9qUts9X7mWJf0+yy2QxJVpjaTGG\n" + + "cmnQKKPeNIhGJk2ENnnnzjEve7L7YJQF6itbx5VCOcsGh3Ocb3YR7DMdWjt7f8pu\n" + + "c6j+q1rP7EpE2afUN/geSlp5i3x8aXZPDj67jImbVCE/Q1X9voCtyzGJH7MXR0N9\n" + + "ZpRF8yzveRfMH8bwAJjSOGAFF5XkcR/RNY95o+J+QcgBLdX48h+ZdNmUf6jqlu3J\n" + + "7KmTXXQcOVpN6dD3CmRFsbjq+x6RHwa8u1iGn+oIkX908r97ckfB/kHKH7ZdXIJc\n" + + "cpxtDQQMGYFpXK/71stq\n" + + "=ozeK\n" + + "-----END PGP SIGNATURE-----"; + + private readonly string signedData = + "tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904\n" + + "parent 8496071c1b46c854b31185ea97743be6a8774479\n" + + "author Ben Burkert 1358451456 -0800\n" + + "committer Ben Burkert 1358451456 -0800\n" + + "\n" + + "a simple commit which works\n"; + + [Fact] + public void CanExtractSignatureFromCommit() + { + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + + // Look up the commit to make sure we wrote something valid + var commit = repo.Lookup(signedId); + Assert.Equal("a simple commit which works\n", commit.Message); + + var signatureInfo = Commit.ExtractSignature(repo, signedId, "gpgsig"); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + + signatureInfo = Commit.ExtractSignature(repo, signedId); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } + + [Fact] + public void CanCreateACommitString() + { + string repoPath = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + var tipCommit = repo.Head.Tip; + var recreatedCommit = Commit.CreateBuffer( + tipCommit.Author, + tipCommit.Committer, + tipCommit.Message, + tipCommit.Tree, + tipCommit.Parents, + false, null); + + var recreatedId = repo.ObjectDatabase.Write(Encoding.UTF8.GetBytes(recreatedCommit)); + Assert.Equal(tipCommit.Id, recreatedId); + } + } + + [Fact] + public void CanCreateASignedCommit() + { + string repoPath = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + var odb = repo.ObjectDatabase; + var signedId = odb.Write(Encoding.UTF8.GetBytes(signedCommit)); + var signedId2 = odb.CreateCommitWithSignature(signedData, signatureData); + + Assert.Equal(signedId, signedId2); + + var signatureInfo = Commit.ExtractSignature(repo, signedId2); + Assert.Equal(signedData, signatureInfo.SignedData); + Assert.Equal(signatureData, signatureInfo.Signature); + } + } } } diff --git a/LibGit2Sharp.Tests/ConfigurationFixture.cs b/LibGit2Sharp.Tests/ConfigurationFixture.cs index ad085a5a0..aaee77b02 100644 --- a/LibGit2Sharp.Tests/ConfigurationFixture.cs +++ b/LibGit2Sharp.Tests/ConfigurationFixture.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; @@ -14,39 +15,10 @@ private static void AssertValueInLocalConfigFile(string repoPath, string regex) AssertValueInConfigFile(configFilePath, regex); } - private static string RetrieveGlobalConfigLocation() - { - string[] variables = { "HOME", "USERPROFILE", }; - - foreach (string variable in variables) - { - string potentialLocation = Environment.GetEnvironmentVariable(variable); - if (string.IsNullOrEmpty(potentialLocation)) - { - continue; - } - - string potentialPath = Path.Combine(potentialLocation, ".gitconfig"); - - if (File.Exists(potentialPath)) - { - return potentialPath; - } - } - - throw new InvalidOperationException("Unable to determine the location of '.gitconfig' file."); - } - - private static void AssertValueInGlobalConfigFile(string regex) - { - string configFilePath = RetrieveGlobalConfigLocation(); - AssertValueInConfigFile(configFilePath, regex); - } - [Fact] public void CanUnsetAnEntryFromTheLocalConfiguration() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.Null(repo.Config.Get("unittests.boolsetting")); @@ -63,11 +35,8 @@ public void CanUnsetAnEntryFromTheLocalConfiguration() [Fact] public void CanUnsetAnEntryFromTheGlobalConfiguration() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - - using (var repo = new Repository(BareTestRepoPath, options)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); Assert.Equal(42, repo.Config.Get("Wow.Man-I-am-totally-global").Value); @@ -80,24 +49,133 @@ public void CanUnsetAnEntryFromTheGlobalConfiguration() } } + [Fact] + public void CanAddAndReadMultivarFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.DoesNotContain(repo.Config.OfType>(), x => x.Key == "unittests.plugin"); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Local); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Local); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanAddAndReadMultivarFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.DoesNotContain(repo.Config.OfType>(), x => x.Key == "unittests.plugin"); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheGlobalConfiguration() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1", ConfigurationLevel.Global); + repo.Config.Add("unittests.plugin", "value2", ConfigurationLevel.Global); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Global) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin", ConfigurationLevel.Global); + + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + } + } + + [Fact] + public void CanUnsetAllFromTheLocalConfiguration() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); + Assert.Empty(repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin") + .Select(x => x.Value) + .ToArray()); + + repo.Config.Add("unittests.plugin", "value1"); + repo.Config.Add("unittests.plugin", "value2"); + + Assert.Equal(2, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .Count()); + + repo.Config.UnsetAll("unittests.plugin"); + + Assert.DoesNotContain(repo.Config.OfType>(), x => x.Key == "unittests.plugin"); + } + } + [Fact] public void CanReadBooleanValue() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.True(repo.Config.Get("core.ignorecase").Value); Assert.True(repo.Config.GetValueOrDefault("core.ignorecase")); - Assert.Equal(false, repo.Config.GetValueOrDefault("missing.key")); - Assert.Equal(true, repo.Config.GetValueOrDefault("missing.key", true)); - Assert.Equal(true, repo.Config.GetValueOrDefault("missing.key", () => true)); + Assert.False(repo.Config.GetValueOrDefault("missing.key")); + Assert.True(repo.Config.GetValueOrDefault("missing.key", true)); + Assert.True(repo.Config.GetValueOrDefault("missing.key", () => true)); } } [Fact] public void CanReadIntValue() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal(2, repo.Config.Get("unittests.intsetting").Value); Assert.Equal(2, repo.Config.GetValueOrDefault("unittests.intsetting")); @@ -112,7 +190,8 @@ public void CanReadIntValue() [Fact] public void CanReadLongValue() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal(15234, repo.Config.Get("unittests.longsetting").Value); Assert.Equal(15234, repo.Config.GetValueOrDefault("unittests.longsetting")); @@ -126,7 +205,8 @@ public void CanReadLongValue() [Fact] public void CanReadStringValue() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.Get("remote.origin.fetch").Value); Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.Get("remote", "origin", "fetch").Value); @@ -136,26 +216,26 @@ public void CanReadStringValue() Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault("remote", "origin", "fetch")); Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault(new[] { "remote", "origin", "fetch" })); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing.key")); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing.key", default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local)); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local)); + Assert.Null(repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing.key", ConfigurationLevel.Local, () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing", "config", "key")); - Assert.Equal(null, repo.Config.GetValueOrDefault("missing", "config", "key", default(string))); + Assert.Null(repo.Config.GetValueOrDefault("missing", "config", "key")); + Assert.Null(repo.Config.GetValueOrDefault("missing", "config", "key", default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault("missing", "config", "key", default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault("missing", "config", "key", "value")); Assert.Equal("value", repo.Config.GetValueOrDefault("missing", "config", "key", () => "value")); - Assert.Equal(null, repo.Config.GetValueOrDefault(new[] { "missing", "key" })); - Assert.Equal(null, repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(string))); + Assert.Null(repo.Config.GetValueOrDefault(new[] { "missing", "key" })); + Assert.Null(repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(string))); Assert.Throws(() => repo.Config.GetValueOrDefault(new[] { "missing", "key" }, default(Func))); Assert.Equal("value", repo.Config.GetValueOrDefault(new[] { "missing", "key" }, "value")); Assert.Equal("value", repo.Config.GetValueOrDefault(new[] { "missing", "key" }, () => "value")); @@ -165,11 +245,10 @@ public void CanReadStringValue() [Fact] public void CanEnumerateGlobalConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - - using (var repo = new Repository(StandardTestRepoPath, options)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var entry = repo.Config.FirstOrDefault>(e => e.Key == "user.name"); Assert.NotNull(entry); Assert.Equal(Constants.Signature.Name, entry.Value); @@ -179,7 +258,8 @@ public void CanEnumerateGlobalConfig() [Fact] public void CanEnumerateLocalConfig() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var entry = repo.Config.FirstOrDefault>(e => e.Key == "core.ignorecase"); Assert.NotNull(entry); @@ -190,7 +270,8 @@ public void CanEnumerateLocalConfig() [Fact] public void CanEnumerateLocalConfigContainingAKeyWithNoValue() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var entry = repo.Config .Single>(c => c.Level == ConfigurationLevel.Local && c.Key == "core.pager"); @@ -202,28 +283,31 @@ public void CanEnumerateLocalConfigContainingAKeyWithNoValue() [Fact] public void CanFindInLocalConfig() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var matches = repo.Config.Find("unit"); Assert.NotNull(matches); Assert.Equal(new[] { "unittests.intsetting", "unittests.longsetting" }, - matches.Select(m => m.Key).ToArray()); + matches + .Select(m => m.Key) + .OrderBy(s => s) + .ToArray()); } } [Fact] public void CanFindInGlobalConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - using (var repo = new Repository(StandardTestRepoPath, options)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - var matches = repo.Config.Find(@"\.name", ConfigurationLevel.Global); + var matches = repo.Config.Find("-rocks", ConfigurationLevel.Global); Assert.NotNull(matches); - Assert.Equal(new[] { "user.name" }, + Assert.Equal(new[] { "woot.this-rocks" }, matches.Select(m => m.Key).ToArray()); } } @@ -231,7 +315,7 @@ public void CanFindInGlobalConfig() [Fact] public void CanSetBooleanValue() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("unittests.boolsetting", true); @@ -243,7 +327,7 @@ public void CanSetBooleanValue() [Fact] public void SettingLocalConfigurationOutsideAReposThrows() { - using (var config = new Configuration()) + using (var config = Configuration.BuildFrom(null, null, null, null)) { Assert.Throws(() => config.Set("unittests.intsetting", 3)); } @@ -252,7 +336,7 @@ public void SettingLocalConfigurationOutsideAReposThrows() [Fact] public void CanSetIntValue() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("unittests.intsetting", 3); @@ -264,7 +348,7 @@ public void CanSetIntValue() [Fact] public void CanSetLongValue() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("unittests.longsetting", (long)451); @@ -276,7 +360,7 @@ public void CanSetLongValue() [Fact] public void CanSetStringValue() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("unittests.stringsetting", "val"); @@ -288,7 +372,7 @@ public void CanSetStringValue() [Fact] public void CanSetAndReadUnicodeStringValue() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("unittests.stringsetting", "Juliën"); @@ -310,7 +394,8 @@ public void CanSetAndReadUnicodeStringValue() [Fact] public void ReadingUnsupportedTypeThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Config.Get("unittests.setting")); Assert.Throws(() => repo.Config.Get("unittests.setting")); @@ -320,7 +405,8 @@ public void ReadingUnsupportedTypeThrows() [Fact] public void ReadingValueThatDoesntExistReturnsNull() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Config.Get("unittests.ghostsetting")); Assert.Null(repo.Config.Get("unittests.ghostsetting")); @@ -332,7 +418,8 @@ public void ReadingValueThatDoesntExistReturnsNull() [Fact] public void SettingUnsupportedTypeThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Config.Set("unittests.setting", (short)123)); Assert.Throws(() => repo.Config.Set("unittests.setting", repo.Config)); @@ -342,12 +429,8 @@ public void SettingUnsupportedTypeThrows() [Fact] public void CanGetAnEntryFromASpecificStore() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path, options)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.True(repo.Config.HasConfig(ConfigurationLevel.Local)); Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); @@ -367,16 +450,180 @@ public void CanGetAnEntryFromASpecificStore() [Fact] public void CanTellIfASpecificStoreContainsAKey() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - - using (var repo = new Repository(BareTestRepoPath, options)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.True(repo.Config.HasConfig(ConfigurationLevel.System)); Assert.Null(repo.Config.Get("MCHammer.You-cant-touch-this", ConfigurationLevel.System)); } } + + public static IEnumerable ConfigAccessors + { + get + { + return new List + { + new[] { new Func(p => Path.Combine(p, ".git", "config")) }, + new[] { new Func(p => Path.Combine(p, ".git")) }, + new[] { new Func(p => p) }, + }; + } + } + + [Theory, MemberData(nameof(ConfigAccessors))] + public void CanAccessConfigurationWithoutARepository(Func localConfigurationPathProvider) + { + var path = SandboxStandardTestRepoGitDir(); + + using (var repo = new Repository(path)) + { + repo.Config.Set("my.key", "local"); + repo.Config.Set("my.key", "mouse", ConfigurationLevel.Global); + } + + var globalPath = Path.Combine(GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global).Single(), ".gitconfig"); + using (var config = Configuration.BuildFrom(localConfigurationPathProvider(path), globalPath)) + { + Assert.Equal("local", config.Get("my.key").Value); + Assert.Equal("mouse", config.Get("my.key", ConfigurationLevel.Global).Value); + } + } + + [Fact] + public void PassingANonExistingLocalConfigurationFileToBuildFromthrowss() + { + Assert.Throws(() => Configuration.BuildFrom( + Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()))); + } + + [Theory] + [InlineData(null, "x@example.com")] + [InlineData("", "x@example.com")] + [InlineData("X", null)] + [InlineData("X", "")] + public void CannotBuildAProperSignatureFromConfigWhenFullIdentityCannotBeFoundInTheConfig(string name, string email) + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, name, email); + Assert.Equal(name, repo.Config.GetValueOrDefault("user.name")); + Assert.Equal(email, repo.Config.GetValueOrDefault("user.email")); + + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + Assert.Null(signature); + } + } + + [Fact] + public void CanSetAndGetSearchPath() + { + string globalPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + string systemPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + string xdgPath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, globalPath); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, systemPath); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, xdgPath); + + Assert.Equal(globalPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global).Single()); + Assert.Equal(systemPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.System).Single()); + Assert.Equal(xdgPath, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Xdg).Single()); + + // reset the search paths to their defaults + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, null); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, null); + } + + [Fact] + public void CanSetAndGetMultipleSearchPaths() + { + string[] paths = + { + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()), + }; + + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, paths); + + Assert.Equal(paths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + + // set back to the defaults + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } + + [Fact] + public void CanResetSearchPaths() + { + // record the default search path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + var oldPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + Assert.NotNull(oldPaths); + + // generate a non-default path to set + var newPaths = new string[] { Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()) }; + + // change to the non-default path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, newPaths); + Assert.Equal(newPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + + // set it back to the default + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + Assert.Equal(oldPaths, GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global)); + } + + [Fact] + public void CanAppendToSearchPaths() + { + string appendMe = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); + var prevPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + + // append using the special name $PATH + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, "$PATH", appendMe); + + var currentPaths = GlobalSettings.GetConfigSearchPaths(ConfigurationLevel.Global); + Assert.Equal(prevPaths.Concat(new[] { appendMe }), currentPaths); + + // set it back to the default + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } + + [Fact] + public void CanRedirectConfigAccess() + { + var scd1 = BuildSelfCleaningDirectory(); + var scd2 = BuildSelfCleaningDirectory(); + + Touch(scd1.RootedDirectoryPath, ".gitconfig"); + Touch(scd2.RootedDirectoryPath, ".gitconfig"); + + // redirect global access to the first path + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd1.RootedDirectoryPath); + + // set a value in the first config + using (var config = Configuration.BuildFrom(null)) + { + config.Set("luggage.code", 9876, ConfigurationLevel.Global); + Assert.Equal(9876, config.Get("luggage.code", ConfigurationLevel.Global).Value); + } + + // redirect global config access to path2 + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, scd2.RootedDirectoryPath); + + // if the redirect succeeds, the value set in the prior config should not be visible + using (var config = Configuration.BuildFrom(null)) + { + Assert.Equal(-1, config.GetValueOrDefault("luggage.code", ConfigurationLevel.Global, -1)); + } + + // reset the search path to the default + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, null); + } } } diff --git a/LibGit2Sharp.Tests/ConflictFixture.cs b/LibGit2Sharp.Tests/ConflictFixture.cs index 9a98bd1b6..6317bf431 100644 --- a/LibGit2Sharp.Tests/ConflictFixture.cs +++ b/LibGit2Sharp.Tests/ConflictFixture.cs @@ -47,26 +47,26 @@ private static List RenameConflictData } [Theory] - [InlineData(true, "ancestor-and-ours.txt", true, false, FileStatus.Removed, 2)] - [InlineData(false, "ancestor-and-ours.txt", true, true, FileStatus.Removed |FileStatus.Untracked, 2)] + [InlineData(true, "ancestor-and-ours.txt", true, false, FileStatus.DeletedFromIndex, 2)] + [InlineData(false, "ancestor-and-ours.txt", true, true, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, 2)] [InlineData(true, "ancestor-and-theirs.txt", true, false, FileStatus.Nonexistent, 2)] - [InlineData(false, "ancestor-and-theirs.txt", true, true, FileStatus.Untracked, 2)] + [InlineData(false, "ancestor-and-theirs.txt", true, true, FileStatus.NewInWorkdir, 2)] [InlineData(true, "ancestor-only.txt", false, false, FileStatus.Nonexistent, 1)] [InlineData(false, "ancestor-only.txt", false, false, FileStatus.Nonexistent, 1)] - [InlineData(true, "conflicts-one.txt", true, false, FileStatus.Removed, 3)] - [InlineData(false, "conflicts-one.txt", true, true, FileStatus.Removed | FileStatus.Untracked, 3)] - [InlineData(true, "conflicts-two.txt", true, false, FileStatus.Removed, 3)] - [InlineData(false, "conflicts-two.txt", true, true, FileStatus.Removed | FileStatus.Untracked, 3)] - [InlineData(true, "ours-and-theirs.txt", true, false, FileStatus.Removed, 2)] - [InlineData(false, "ours-and-theirs.txt", true, true, FileStatus.Removed | FileStatus.Untracked, 2)] - [InlineData(true, "ours-only.txt", true, false, FileStatus.Removed, 1)] - [InlineData(false, "ours-only.txt", true, true, FileStatus.Removed | FileStatus.Untracked, 1)] + [InlineData(true, "conflicts-one.txt", true, false, FileStatus.DeletedFromIndex, 3)] + [InlineData(false, "conflicts-one.txt", true, true, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, 3)] + [InlineData(true, "conflicts-two.txt", true, false, FileStatus.DeletedFromIndex, 3)] + [InlineData(false, "conflicts-two.txt", true, true, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, 3)] + [InlineData(true, "ours-and-theirs.txt", true, false, FileStatus.DeletedFromIndex, 2)] + [InlineData(false, "ours-and-theirs.txt", true, true, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, 2)] + [InlineData(true, "ours-only.txt", true, false, FileStatus.DeletedFromIndex, 1)] + [InlineData(false, "ours-only.txt", true, true, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, 1)] [InlineData(true, "theirs-only.txt", true, false, FileStatus.Nonexistent, 1)] - [InlineData(false, "theirs-only.txt", true, true, FileStatus.Untracked, 1)] + [InlineData(false, "theirs-only.txt", true, true, FileStatus.NewInWorkdir, 1)] public void CanResolveConflictsByRemovingFromTheIndex( bool removeFromWorkdir, string filename, bool existsBeforeRemove, bool existsAfterRemove, FileStatus lastStatus, int removedIndexEntries) { - var path = CloneMergedTestRepo(); + var path = SandboxMergedTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; @@ -75,16 +75,16 @@ public void CanResolveConflictsByRemovingFromTheIndex( Assert.Equal(existsBeforeRemove, File.Exists(fullpath)); Assert.NotNull(repo.Index.Conflicts[filename]); - Assert.Equal(0, repo.Index.Conflicts.ResolvedConflicts.Count()); + Assert.Empty(repo.Index.Conflicts.ResolvedConflicts); - repo.Index.Remove(filename, removeFromWorkdir); + Commands.Remove(repo, filename, removeFromWorkdir); Assert.Null(repo.Index.Conflicts[filename]); Assert.Equal(count - removedIndexEntries, repo.Index.Count); Assert.Equal(existsAfterRemove, File.Exists(fullpath)); - Assert.Equal(lastStatus, repo.Index.RetrieveStatus(filename)); + Assert.Equal(lastStatus, repo.RetrieveStatus(filename)); - Assert.Equal(1, repo.Index.Conflicts.ResolvedConflicts.Count()); + Assert.Single(repo.Index.Conflicts.ResolvedConflicts); Assert.NotNull(repo.Index.Conflicts.ResolvedConflicts[filename]); } } @@ -92,7 +92,7 @@ public void CanResolveConflictsByRemovingFromTheIndex( [Fact] public void CanGetOriginalNamesOfRenameConflicts() { - var path = CloneMergeRenamesTestRepo(); + var path = Sandbox(MergeRenamesTestRepoWorkingDirPath); using (var repo = new Repository(path)) { var expected = RenameConflictData; @@ -101,7 +101,7 @@ public void CanGetOriginalNamesOfRenameConflicts() Assert.Equal(expected.Count, actual.Count()); int i = 0; - foreach(var name in actual) + foreach (var name in actual) { Assert.Equal(expected[i][0], name.Ancestor); Assert.Equal(expected[i][1], name.Ours); @@ -112,10 +112,11 @@ public void CanGetOriginalNamesOfRenameConflicts() } } - [Theory, PropertyData("ConflictData")] + [Theory, MemberData(nameof(ConflictData))] public void CanRetrieveSingleConflictByPath(string filepath, string ancestorId, string ourId, string theirId) { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { Conflict conflict = repo.Index.Conflicts[filepath]; Assert.NotNull(conflict); @@ -162,7 +163,8 @@ private string GetId(IndexEntry e) [Fact] public void CanRetrieveAllConflicts() { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { var expected = repo.Index.Conflicts.Select(c => new[] { GetPath(c), GetId(c.Ancestor), GetId(c.Ours), GetId(c.Theirs) }).ToArray(); Assert.Equal(expected, ConflictData); diff --git a/LibGit2Sharp.Tests/CurrentOperationFixture.cs b/LibGit2Sharp.Tests/CurrentOperationFixture.cs index 5b9200310..8944bbeb4 100644 --- a/LibGit2Sharp.Tests/CurrentOperationFixture.cs +++ b/LibGit2Sharp.Tests/CurrentOperationFixture.cs @@ -23,7 +23,8 @@ public void CurrentOperationIsNoneForNewRepo(bool isBare) [Fact] public void CurrentOperationInNoneForABareRepo() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); } @@ -41,7 +42,7 @@ public void CurrentOperationInNoneForABareRepo() [InlineData("rebase-merge/whatever", CurrentOperation.RebaseMerge)] public void CurrentOperationHasExpectedPendingOperationValues(string stateFile, CurrentOperation expectedState) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); Touch(Path.Combine(path, ".git"), stateFile); diff --git a/LibGit2Sharp.Tests/DescribeFixture.cs b/LibGit2Sharp.Tests/DescribeFixture.cs new file mode 100644 index 000000000..bb2cacd06 --- /dev/null +++ b/LibGit2Sharp.Tests/DescribeFixture.cs @@ -0,0 +1,84 @@ +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using System; + +namespace LibGit2Sharp.Tests +{ + public class DescribeFixture : BaseFixture + { + [Fact] + public void CanDescribeACommit() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + // No annotated tags can be used to describe "master" + var masterTip = repo.Branches["master"].Tip; + Assert.Throws(() => repo.Describe(masterTip)); + Assert.Equal("4c062a6", repo.Describe(masterTip, + new DescribeOptions { UseCommitIdAsFallback = true })); + Assert.Equal("4c06", repo.Describe(masterTip, + new DescribeOptions { UseCommitIdAsFallback = true, MinimumCommitIdAbbreviatedSize = 2 })); + + // No lightweight tags can either be used to describe "master" + Assert.Throws(() => repo.Describe(masterTip, + new DescribeOptions { Strategy = DescribeStrategy.Tags })); + + repo.ApplyTag("myTag", "5b5b025afb0b4c913b4c338a42934a3863bf3644"); + Assert.Equal("myTag-5-g4c062a6", repo.Describe(masterTip, + new DescribeOptions { Strategy = DescribeStrategy.Tags })); + Assert.Equal("myTag-5-g4c062a636", repo.Describe(masterTip, + new DescribeOptions { Strategy = DescribeStrategy.Tags, MinimumCommitIdAbbreviatedSize = 9 })); + Assert.Equal("myTag-4-gbe3563a", repo.Describe(masterTip.Parents.Single(), + new DescribeOptions { Strategy = DescribeStrategy.Tags })); + + Assert.Equal("heads/master", repo.Describe(masterTip, + new DescribeOptions { Strategy = DescribeStrategy.All })); + Assert.Equal("heads/packed-test-3-gbe3563a", repo.Describe(masterTip.Parents.Single(), + new DescribeOptions { Strategy = DescribeStrategy.All })); + + // "test" branch points to an annotated tag (also named "test") + // Let's rename the branch to ease the understanding of what we + // are exercising. + + repo.Branches.Rename(repo.Branches["test"], "ForLackOfABetterName"); + + var anotherTip = repo.Branches["ForLackOfABetterName"].Tip; + Assert.Equal("test", repo.Describe(anotherTip)); + Assert.Equal("test-0-g7b43849", repo.Describe(anotherTip, + new DescribeOptions { AlwaysRenderLongFormat = true })); + } + } + + [Fact] + public void CanFollowFirstParent() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var branch = repo.CreateBranch("branch"); + + // Make an earlier tag on master + repo.Commit("A", Constants.Signature, Constants.Signature, new CommitOptions { AllowEmptyCommit = true }); + repo.ApplyTag("firstParentTag"); + + // Make a later tag on branch + Commands.Checkout(repo, branch); + repo.Commit("B", Constants.Signature, Constants.Signature, new CommitOptions { AllowEmptyCommit = true }); + repo.ApplyTag("mostRecentTag"); + + Commands.Checkout(repo, "master"); + repo.Commit("C", Constants.Signature, Constants.Signature, new CommitOptions { AllowEmptyCommit = true }); + repo.Merge(branch, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastForward }); + + // With OnlyFollowFirstParent = false, the most recent tag reachable should be returned + Assert.Equal("mostRecentTag-3-gf17be71", repo.Describe(repo.Head.Tip, new DescribeOptions { OnlyFollowFirstParent = false, Strategy = DescribeStrategy.Tags })); + + // With OnlyFollowFirstParent = true, the most recent tag on the current branch should be returned + Assert.Equal("firstParentTag-2-gf17be71", repo.Describe(repo.Head.Tip, new DescribeOptions { OnlyFollowFirstParent = true, Strategy = DescribeStrategy.Tags })); + + } + } + } +} diff --git a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs index c0711a468..046fe5214 100644 --- a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs +++ b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -10,7 +11,8 @@ public class DiffBlobToBlobFixture : BaseFixture [Fact] public void ComparingABlobAgainstItselfReturnsNoDifference() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("7909961"); @@ -25,7 +27,8 @@ public void ComparingABlobAgainstItselfReturnsNoDifference() [Fact] public void CanCompareTwoVersionsOfABlobWithADiffOfTwoHunks() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var oldblob = repo.Lookup("7909961"); var newblob = repo.Lookup("4e935b7"); @@ -72,7 +75,7 @@ Blob CreateBinaryBlob(IRepository repo) [Fact] public void CanCompareATextualBlobAgainstABinaryBlob() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Blob binBlob = CreateBinaryBlob(repo); @@ -91,7 +94,8 @@ public void CanCompareATextualBlobAgainstABinaryBlob() [Fact] public void CanCompareABlobAgainstANullBlob() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("7909961"); @@ -112,7 +116,8 @@ public void CanCompareABlobAgainstANullBlob() [Fact] public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { ContentChanges changes = repo.Diff.Compare((Blob)null, (Blob)null); @@ -122,5 +127,119 @@ public void ComparingTwoNullBlobsReturnsAnEmptyContentChanges() Assert.Equal(0, changes.LinesDeleted); } } + + [Fact] + public void ComparingBlobsWithNoSpacesAndIndentHeuristicOptionMakesADifference() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + // Based on test diff indent heuristic from: + // https://github.com/git/git/blob/433860f3d0beb0c6f205290bd16cda413148f098/t/t4061-diff-indent.sh#L17 + var oldContent = +@" 1 + 2 + a + + b + 3 + 4"; + var newContent = +@" 1 + 2 + a + + b + a + + b + 3 + 4"; + var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); + var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); + var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; + var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; + + ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); + ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); + + Assert.NotEqual(changes0.Patch, changes1.Patch); + Assert.Equal(CanonicalChangedLines(changes0), CanonicalChangedLines(changes1)); + } + } + + [Fact] + public void ComparingBlobsWithNoSpacesIndentHeuristicOptionMakesNoDifference() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var oldContent = +@" 1 + 2 + a + b + 3 + 4"; + var newContent = +@" 1 + 2 + a + b + a + b + 3 + 4"; + var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); + var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); + var noIndentHeuristicOption = new CompareOptions { IndentHeuristic = false }; + var indentHeuristicOption = new CompareOptions { IndentHeuristic = true }; + + ContentChanges changes0 = repo.Diff.Compare(oldBlob, newBlob, noIndentHeuristicOption); + ContentChanges changes1 = repo.Diff.Compare(oldBlob, newBlob, indentHeuristicOption); + + Assert.Equal(changes0.Patch, changes1.Patch); + } + } + + [Fact] + public void DiffSetsTheAddedAndDeletedLinesCorrectly() + { + var path = SandboxStandardTestRepoGitDir(); + + using (var repo = new Repository(path)) + { + var oldContent = + @"1 +2 +3 +4"; + + var newContent = + @"1 +2 +3 +5"; + var oldBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(oldContent))); + var newBlob = repo.ObjectDatabase.CreateBlob(new MemoryStream(Encoding.UTF8.GetBytes(newContent))); + + ContentChanges changes = repo.Diff.Compare(oldBlob, newBlob); + + Assert.Single(changes.AddedLines); + Assert.Single(changes.DeletedLines); + + Assert.Equal("4", changes.DeletedLines.First().Content); + Assert.Equal("5", changes.AddedLines.First().Content); + + Assert.Equal(4, changes.DeletedLines.First().LineNumber); + Assert.Equal(4, changes.AddedLines.First().LineNumber); + } + } + + static string CanonicalChangedLines(ContentChanges changes) + { + // Create an ordered representation of lines that have been added or removed + return string.Join("\n", changes.Patch.Split('\n').Where(l => l.StartsWith("+") || l.StartsWith("-")).OrderBy(l => l)); + } } } diff --git a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs index d58508ca8..b712a214b 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs @@ -12,12 +12,12 @@ private static void SetUpSimpleDiffContext(IRepository repo) { var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "hello\n"); - repo.Index.Stage(fullpath); + Commands.Stage(repo, fullpath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); File.AppendAllText(fullpath, "world\n"); - repo.Index.Stage(fullpath); + Commands.Stage(repo, fullpath); File.AppendAllText(fullpath, "!!!\n"); } @@ -43,46 +43,53 @@ public void CanCompareASimpleTreeAgainstTheWorkDir() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..4f125e3 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,3 @@\n") + .Append(" hello\n") + .Append("+world\n") + .Append("+!!!\n"); + + Assert.Equal(expected.ToString(), patch); + } } } [Fact] public void CanCompareAMoreComplexTreeAgainstTheWorkdir() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.WorkingDirectory); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.WorkingDirectory)) + { + Assert.NotNull(changes); - Assert.Equal(6, changes.Count()); + Assert.Equal(6, changes.Count()); - Assert.Equal(new[] { "deleted_staged_file.txt", "deleted_unstaged_file.txt" }, - changes.Deleted.Select(tec => tec.Path)); + Assert.Equal(new[] { "deleted_staged_file.txt", "deleted_unstaged_file.txt" }, + changes.Deleted.Select(tec => tec.Path)); - Assert.Equal(new[] { "new_tracked_file.txt", "new_untracked_file.txt" }, - changes.Added.Select(tec => tec.Path)); + Assert.Equal(new[] { "new_tracked_file.txt", "new_untracked_file.txt" }, + changes.Added.Select(tec => tec.Path)); - Assert.Equal(new[] { "modified_staged_file.txt", "modified_unstaged_file.txt" }, - changes.Modified.Select(tec => tec.Path)); + Assert.Equal(new[] { "modified_staged_file.txt", "modified_unstaged_file.txt" }, + changes.Modified.Select(tec => tec.Path)); + } } } @@ -106,23 +113,27 @@ public void CanCompareASimpleTreeAgainstTheWorkDirAndTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..4f125e3 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,3 @@\n") + .Append(" hello\n") + .Append("+world\n") + .Append("+!!!\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -158,50 +169,56 @@ public void ShowcaseTheDifferenceBetweenTheTwoKindOfComparison() var fullpath = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); File.Move(fullpath, fullpath + ".bak"); - repo.Index.Stage(fullpath); + Commands.Stage(repo, fullpath); File.Move(fullpath + ".bak", fullpath); - FileStatus state = repo.Index.RetrieveStatus("file.txt"); - Assert.Equal(FileStatus.Removed | FileStatus.Untracked, state); - - var wrkDirToIdxToTree = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - - Assert.Equal(1, wrkDirToIdxToTree.Deleted.Count()); - Assert.Equal(0, wrkDirToIdxToTree.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index | DiffTargets.WorkingDirectory); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("deleted file mode 100644\n") - .Append("index ce01362..0000000\n") - .Append("--- a/file.txt\n") - .Append("+++ /dev/null\n") - .Append("@@ -1 +0,0 @@\n") - .Append("-hello\n"); - - Assert.Equal(expected.ToString(), patch); - - var wrkDirToTree = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - - Assert.Equal(0, wrkDirToTree.Deleted.Count()); - Assert.Equal(1, wrkDirToTree.Modified.Count()); - - patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.WorkingDirectory); - expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..4f125e3 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,3 @@\n") - .Append(" hello\n") - .Append("+world\n") - .Append("+!!!\n"); - - Assert.Equal(expected.ToString(), patch); + FileStatus state = repo.RetrieveStatus("file.txt"); + Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, state); + + using (var wrkDirToIdxToTree = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + Assert.Single(wrkDirToIdxToTree.Deleted); + Assert.Empty(wrkDirToIdxToTree.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index | DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("deleted file mode 100644\n") + .Append("index ce01362..0000000\n") + .Append("--- a/file.txt\n") + .Append("+++ /dev/null\n") + .Append("@@ -1 +0,0 @@\n") + .Append("-hello\n"); + + Assert.Equal(expected.ToString(), patch); + } + + using (var wrkDirToTree = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + Assert.Empty(wrkDirToTree.Deleted); + Assert.Single(wrkDirToTree.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..4f125e3 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,3 @@\n") + .Append(" hello\n") + .Append("+world\n") + .Append("+!!!\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -224,22 +241,26 @@ public void CanCompareASimpleTreeAgainstTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, - DiffTargets.Index); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index ce01362..94954ab 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1,2 @@\n") - .Append(" hello\n") - .Append("+world\n"); - - Assert.Equal(expected.ToString(), patch); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.Index)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index ce01362..94954ab 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1,2 @@\n") + .Append(" hello\n") + .Append("+world\n"); + + Assert.Equal(expected.ToString(), patch); + } } } @@ -270,17 +291,20 @@ public void CanCompareASimpleTreeAgainstTheIndex() [Fact] public void CanCompareAMoreComplexTreeAgainstTheIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index)) + { + Assert.NotNull(changes); - Assert.Equal(3, changes.Count()); - Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("new_tracked_file.txt", changes.Added.Single().Path); - Assert.Equal("modified_staged_file.txt", changes.Modified.Single().Path); + Assert.Equal(3, changes.Count()); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("new_tracked_file.txt", changes.Added.Single().Path); + Assert.Equal("modified_staged_file.txt", changes.Modified.Single().Path); + } } } @@ -297,48 +321,56 @@ public void CanCompareAMoreComplexTreeAgainstTheIndex() [Fact] public void CanCompareASubsetofTheTreeAgainstTheIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt" }); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt" })) + { + Assert.NotNull(changes); - Assert.NotNull(changes); - - Assert.Equal(1, changes.Count()); - Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + Assert.Single(changes); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + } } } private static void AssertCanCompareASubsetOfTheTreeAgainstTheIndex(TreeChanges changes) { Assert.NotNull(changes); - Assert.Equal(1, changes.Count()); + Assert.Single(changes); Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); } [Fact] public void CanCompareASubsetofTheTreeAgainstTheIndexWithLaxExplicitPathsValidationAndANonExistentPath() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); - - changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }); - AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + } + + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" })) + { + AssertCanCompareASubsetOfTheTreeAgainstTheIndex(changes); + } } } [Fact] public void ComparingASubsetofTheTreeAgainstTheIndexWithStrictExplicitPathsValidationAndANonExistentPathThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; @@ -373,36 +405,41 @@ public void CanCopeWithEndOfFileNewlineChanges() { var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "a"); - repo.Index.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); repo.Commit("Add file without line ending", Constants.Signature, Constants.Signature); File.AppendAllText(fullpath, "\n"); - repo.Index.Stage("file.txt"); - - var changes = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index); - var expected = new StringBuilder() - .Append("diff --git a/file.txt b/file.txt\n") - .Append("index 2e65efe..7898192 100644\n") - .Append("--- a/file.txt\n") - .Append("+++ b/file.txt\n") - .Append("@@ -1 +1 @@\n") - .Append("-a\n") - .Append("\\ No newline at end of file\n") - .Append("+a\n"); - - Assert.Equal(expected.ToString(), patch); - Assert.Equal(1, patch.LinesAdded); - Assert.Equal(1, patch.LinesDeleted); + Commands.Stage(repo, "file.txt"); + + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)) + { + Assert.Single(changes.Modified); + } + + using (var patch = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)) + { + var expected = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index 2e65efe..7898192 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1 +1 @@\n") + .Append("-a\n") + .Append("\\ No newline at end of file\n") + .Append("+a\n"); + + Assert.Equal(expected.ToString(), patch); + Assert.Equal(1, patch.LinesAdded); + Assert.Equal(1, patch.LinesDeleted); + } } } [Fact] public void ComparingATreeInABareRepositoryAgainstTheWorkDirOrTheIndexThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws( () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.WorkingDirectory)); @@ -422,13 +459,14 @@ public void CanCompareANullTreeAgainstTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.Index); + using (var changes = repo.Diff.Compare(null, + DiffTargets.Index)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); - - Assert.Equal("file.txt", changes.Added.Single().Path); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } @@ -441,13 +479,14 @@ public void CanCompareANullTreeAgainstTheWorkdir() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.WorkingDirectory); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(null, + DiffTargets.WorkingDirectory)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal("file.txt", changes.Added.Single().Path); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } @@ -460,13 +499,34 @@ public void CanCompareANullTreeAgainstTheWorkdirAndTheIndex() { SetUpSimpleDiffContext(repo); - var changes = repo.Diff.Compare(null, - DiffTargets.WorkingDirectory | DiffTargets.Index); + using (var changes = repo.Diff.Compare(null, + DiffTargets.WorkingDirectory | DiffTargets.Index)) + { + Assert.Single(changes); + Assert.Single(changes.Added); + + Assert.Equal("file.txt", changes.Added.Single().Path); + } + } + } + + [Fact] + public void CompareSetsCorrectAddedAndDeletedLines() + { + string repoPath = InitNewRepository(); - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var repo = new Repository(repoPath)) + { + SetUpSimpleDiffContext(repo); - Assert.Equal("file.txt", changes.Added.Single().Path); + using (var changes = repo.Diff.Compare(repo.Head.Tip.Tree, + DiffTargets.WorkingDirectory | DiffTargets.Index)) + { + foreach (var entry in changes) + { + Assert.Equal(2, entry.AddedLines.Count()); + } + } } } } diff --git a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs index ea230a037..8c2956331 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -4,40 +4,46 @@ using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { public class DiffTreeToTreeFixture : BaseFixture { - private static readonly string subBranchFilePath = Path.Combine("1", "branch_file.txt"); + private static readonly string subBranchFilePath = string.Join("/", "1", "branch_file.txt"); [Fact] public void ComparingATreeAgainstItselfReturnsNoDifference() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, tree); - var patch = repo.Diff.Compare(tree, tree); + using (var changes = repo.Diff.Compare(tree, tree)) + { + Assert.Empty(changes); + } - Assert.Empty(changes); - Assert.Empty(patch); - Assert.Equal(String.Empty, patch); + using (var patch = repo.Diff.Compare(tree, tree)) + { + Assert.Empty(patch); + Assert.Equal(string.Empty, patch); + } } } [Fact] public void RetrievingANonExistentFileChangeReturnsNull() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; - var changes = repo.Diff.Compare(tree, tree); - - Assert.Null(changes["batman"]); + using (var changes = repo.Diff.Compare(tree, tree)) + { + Assert.Equal(0, changes.Count(c => c.Path == "batman")); + } } } @@ -49,27 +55,31 @@ public void RetrievingANonExistentFileChangeReturnsNull() [Fact] public void CanCompareACommitTreeAgainstItsParent() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree commitTree = repo.Head.Tip.Tree; Tree parentCommitTree = repo.Head.Tip.Parents.Single().Tree; - var changes = repo.Diff.Compare(parentCommitTree, commitTree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - TreeEntryChanges treeEntryChanges = changes["1.txt"]; + TreeEntryChanges treeEntryChanges = changes.Single(c => c.Path == "1.txt"); - var patch = repo.Diff.Compare(parentCommitTree, commitTree); - Assert.False(patch["1.txt"].IsBinaryComparison); + Assert.Equal("1.txt", treeEntryChanges.Path); + Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); - Assert.Equal("1.txt", treeEntryChanges.Path); - Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); + Assert.Equal(treeEntryChanges.Path, changes.Added.Single().Path); - Assert.Equal(treeEntryChanges, changes.Added.Single()); + Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + } - Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + using (var patch = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.False(patch["1.txt"].IsBinaryComparison); + } } } @@ -89,52 +99,52 @@ static void CreateBinaryFile(string path) [Fact] public void CanDetectABinaryChange() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { const string filename = "binfile.foo"; var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); CreateBinaryFile(filepath); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); File.AppendAllText(filepath, "abcdef"); - var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename }); - Assert.True(patch[filename].IsBinaryComparison); + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + Assert.True(patch[filename].IsBinaryComparison); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature); - var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); - Assert.True(patch2[filename].IsBinaryComparison); + using (var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + Assert.True(patch2[filename].IsBinaryComparison); } } [Fact] public void CanDetectABinaryDeletion() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { const string filename = "binfile.foo"; var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); CreateBinaryFile(filepath); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); File.Delete(filepath); - var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new [] {filename}); - Assert.True(patch[filename].IsBinaryComparison); + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + Assert.True(patch[filename].IsBinaryComparison); - repo.Index.Remove(filename); + Commands.Remove(repo, filename); var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature); - var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename }); - Assert.True(patch2[filename].IsBinaryComparison); + using (var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + Assert.True(patch2[filename].IsBinaryComparison); } } @@ -151,16 +161,19 @@ public void CanDetectABinaryDeletion() [Fact] public void CanCompareASubsetofTheTreeAgainstOneOfItsAncestor() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Head.Tip.Tree; Tree ancestor = repo.Lookup("9fd738e").Tree; - var changes = repo.Diff.Compare(ancestor, tree, new[] { "1" }); - Assert.NotNull(changes); + using (var changes = repo.Diff.Compare(ancestor, tree, new[] { "1" })) + { + Assert.NotNull(changes); - Assert.Equal(1, changes.Count()); - Assert.Equal(subBranchFilePath, changes.Added.Single().Path); + Assert.Single(changes); + Assert.Equal(subBranchFilePath, changes.Added.Single().Path); + } } } @@ -181,50 +194,60 @@ public void CanCompareASubsetofTheTreeAgainstOneOfItsAncestor() [Fact] public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree commitTree = repo.Head.Tip.Tree; Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree); - - Assert.Equal(10, changes.Count()); - Assert.Equal(9, changes.Added.Count()); - Assert.Equal(1, changes.Deleted.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree)) + { + Assert.Equal(10, changes.Count()); + Assert.Equal(9, changes.Added.Count()); + Assert.Single(changes.Deleted); - Assert.Equal("readme.txt", changes.Deleted.Single().Path); - Assert.Equal(new[] { "1.txt", subBranchFilePath, "README", "branch_file.txt", "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt" }, - changes.Added.Select(x => x.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); + Assert.Equal("readme.txt", changes.Deleted.Single().Path); + Assert.Equal(new[] { "1.txt", subBranchFilePath, "README", "branch_file.txt", "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt" }, + changes.Added.Select(x => x.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); + } - var patch = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree); - Assert.Equal(9, patch.LinesAdded); - Assert.Equal(2, patch.LinesDeleted); - Assert.Equal(2, patch["readme.txt"].LinesDeleted); + using (var patch = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree)) + { + Assert.Equal(9, patch.LinesAdded); + Assert.Equal(2, patch.LinesDeleted); + Assert.Equal(2, patch["readme.txt"].LinesDeleted); + } } } [Fact] public void CanCompareATreeAgainstAnotherTreeWithLaxExplicitPathsValidationAndNonExistentPath() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree commitTree = repo.Head.Tip.Tree; Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, - new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } - changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, - new[] { "if-I-exist-this-test-is-really-unlucky.txt" }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" })) + { + Assert.Empty(changes); + } } } [Fact] public void ComparingATreeAgainstAnotherTreeWithStrictExplicitPathsValidationThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree commitTree = repo.Head.Tip.Tree; Tree commitTreeWithDifferentAncestor = repo.Branches["refs/remotes/origin/test"].Tip.Tree; @@ -256,17 +279,18 @@ public void ComparingATreeAgainstAnotherTreeWithStrictExplicitPathsValidationThr [Fact] public void DetectsTheRenamingOfAModifiedFileByDefault() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree commitTreeWithRenamedFile = repo.Lookup("4be51d6").Tree; - var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile); - - Assert.Equal(1, changes.Count()); - Assert.Equal("super-file.txt", changes["super-file.txt"].Path); - Assert.Equal("my-name-does-not-feel-right.txt", changes["super-file.txt"].OldPath); - Assert.Equal(1, changes.Renamed.Count()); + using (var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile)) + { + Assert.Single(changes); + Assert.Equal("my-name-does-not-feel-right.txt", changes.Single(c => c.Path == "super-file.txt").OldPath); + Assert.Single(changes.Renamed); + } } } @@ -282,20 +306,21 @@ public void DetectsTheExactRenamingOfFilesByDefault() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Index.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Single(changes); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + } } } @@ -317,14 +342,14 @@ public void RenameThresholdsAreObeyed() // 4 lines Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); // 8 lines, 50% are from original file Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\ne\nf\ng\nh\n"); - repo.Index.Stage(originalPath); - repo.Index.Move(originalPath, renamedPath); + Commands.Stage(repo, originalPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); @@ -337,12 +362,14 @@ public void RenameThresholdsAreObeyed() }; compareOptions.Similarity.RenameThreshold = 30; - var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions); - Assert.True(changes.All(x => x.Status == ChangeKind.Renamed)); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions)) + Assert.True(changes.All(x => x.Status == ChangeKind.Renamed)); compareOptions.Similarity.RenameThreshold = 90; - changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions); - Assert.False(changes.Any(x => x.Status == ChangeKind.Renamed)); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions)) + Assert.DoesNotContain(changes, x => x.Status == ChangeKind.Renamed); } } @@ -358,24 +385,25 @@ public void ExactModeDetectsExactRenames() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Index.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); + })) + { + Assert.Single(changes); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + } } } @@ -392,22 +420,23 @@ public void ExactModeDetectsExactCopies() var copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + } } } @@ -423,26 +452,27 @@ public void ExactModeDoesntDetectRenamesWithEdits() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Index.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, renamedPath), "e\nf\n"); - repo.Index.Stage(renamedPath); + Commands.Stage(repo, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Exact, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Renamed.Count()); - Assert.Equal(1, changes.Added.Count()); - Assert.Equal(1, changes.Deleted.Count()); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + Assert.Single(changes.Added); + Assert.Single(changes.Deleted); + } } } @@ -460,28 +490,29 @@ public void CanIncludeUnmodifiedEntriesWhenDetectingTheExactRenamingOfFilesWhenE Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, IncludeUnmodified = true, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Unmodified.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Unmodified); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -497,23 +528,24 @@ public void CanNotDetectTheExactRenamingFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - repo.Index.Move(originalPath, renamedPath); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.None, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Renamed.Count()); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + } } } @@ -531,26 +563,27 @@ public void CanDetectTheExactCopyingOfNonModifiedFilesWhenEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, - }); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -568,19 +601,20 @@ public void CanNotDetectTheExactCopyingOfNonModifiedFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(1, changes.Count()); - Assert.Equal(0, changes.Copied.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Single(changes); + Assert.Empty(changes.Copied); + } } } @@ -598,29 +632,30 @@ public void CanDetectTheExactCopyingOfModifiedFilesWhenEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); Touch(repo.Info.WorkingDirectory, originalPath, "e\n"); - repo.Index.Stage(originalPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.Copies, - }); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Copied.Count()); - Assert.Equal(originalPath, changes.Copied.Single().OldPath); - Assert.Equal(copiedPath, changes.Copied.Single().Path); + })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } } } @@ -638,22 +673,23 @@ public void CanNotDetectTheExactCopyingOfModifiedFilesWhenNotEnabled() Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); - repo.Index.Stage(originalPath); + Commands.Stage(repo, originalPath); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.Copy(originalFullPath, copiedFullPath); File.AppendAllText(originalFullPath, "e\n"); - repo.Index.Stage(originalPath); - repo.Index.Stage(copiedPath); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree); - - Assert.Equal(2, changes.Count()); - Assert.Equal(0, changes.Copied.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Copied); + } } } @@ -667,19 +703,20 @@ public void CanIncludeUnmodifiedEntriesWhenEnabled() Touch(repo.Info.WorkingDirectory, "a.txt", "abc\ndef\n"); Touch(repo.Info.WorkingDirectory, "b.txt", "abc\ndef\n"); - repo.Index.Stage(new[] {"a.txt", "b.txt"}); + Commands.Stage(repo, new[] { "a.txt", "b.txt" }); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "b.txt"), "ghi\njkl\n"); - repo.Index.Stage("b.txt"); + Commands.Stage(repo, "b.txt"); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, - compareOptions: new CompareOptions {IncludeUnmodified = true}); - - Assert.Equal(2, changes.Count()); - Assert.Equal(1, changes.Unmodified.Count()); - Assert.Equal(1, changes.Modified.Count()); + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: new CompareOptions { IncludeUnmodified = true })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Unmodified); + Assert.Single(changes.Modified); + } } } @@ -690,20 +727,20 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh var path = Repository.Init(scd.DirectoryPath); using (var repo = new Repository(path)) { - const string originalPath = "original.txt"; - const string renamedPath = "renamed.txt"; + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; const string originalPath2 = "original2.txt"; - const string copiedPath1 = "copied.txt"; + const string copiedPath1 = "copied.txt"; const string originalPath3 = "original3.txt"; - const string copiedPath2 = "copied2.txt"; + const string copiedPath2 = "copied2.txt"; Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); Touch(repo.Info.WorkingDirectory, originalPath2, "1\n2\n3\n4\n"); Touch(repo.Info.WorkingDirectory, originalPath3, "5\n6\n7\n8\n"); - repo.Index.Stage(originalPath); - repo.Index.Stage(originalPath2); - repo.Index.Stage(originalPath3); + Commands.Stage(repo, originalPath); + Commands.Stage(repo, originalPath2); + Commands.Stage(repo, originalPath3); Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); @@ -715,30 +752,31 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh File.Copy(originalFullPath3, copiedFullPath2); File.AppendAllText(originalFullPath3, "9\n"); - repo.Index.Stage(originalPath3); - repo.Index.Stage(copiedPath1); - repo.Index.Stage(copiedPath2); - repo.Index.Move(originalPath, renamedPath); + Commands.Stage(repo, originalPath3); + Commands.Stage(repo, copiedPath1); + Commands.Stage(repo, copiedPath2); + Commands.Move(repo, originalPath, renamedPath); Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); - var changes = repo.Diff.Compare(old.Tree, @new.Tree, + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: new CompareOptions { Similarity = SimilarityOptions.CopiesHarder, - }); - - Assert.Equal(4, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.Renamed.Count()); - Assert.Equal(originalPath, changes.Renamed.Single().OldPath); - Assert.Equal(renamedPath, changes.Renamed.Single().Path); - Assert.Equal(2, changes.Copied.Count()); - Assert.Equal(originalPath2, changes.Copied.ElementAt(0).OldPath); - Assert.Equal(copiedPath1, changes.Copied.ElementAt(0).Path); - Assert.Equal(originalPath3, changes.Copied.ElementAt(1).OldPath); - Assert.Equal(copiedPath2, changes.Copied.ElementAt(1).Path); + })) + { + Assert.Equal(4, changes.Count()); + Assert.Single(changes.Modified); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + Assert.Equal(2, changes.Copied.Count()); + Assert.Equal(originalPath2, changes.Copied.ElementAt(0).OldPath); + Assert.Equal(copiedPath1, changes.Copied.ElementAt(0).Path); + Assert.Equal(originalPath3, changes.Copied.ElementAt(1).OldPath); + Assert.Equal(copiedPath2, changes.Copied.ElementAt(1).Path); + } } } /* @@ -771,27 +809,30 @@ public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWh [InlineData(4, 193)] public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion(int contextLines, int expectedPatchLength) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree commitTreeWithUpdatedFile = repo.Lookup("ec9e401").Tree; - var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - - var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile, - compareOptions: new CompareOptions { ContextLines = contextLines }); + using (var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + Assert.Single(changes); + Assert.Single(changes.Modified); + } - Assert.Equal(expectedPatchLength, patch.Content.Length); + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile, + compareOptions: new CompareOptions { ContextLines = contextLines })) + { + Assert.Equal(expectedPatchLength, patch.Content.Length); - PatchEntryChanges entryChanges = patch["numbers.txt"]; + PatchEntryChanges entryChanges = patch["numbers.txt"]; - Assert.Equal(2, entryChanges.LinesAdded); - Assert.Equal(1, entryChanges.LinesDeleted); - Assert.Equal(expectedPatchLength, entryChanges.Patch.Length); - Assert.Equal("numbers.txt", entryChanges.Path); + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(expectedPatchLength, entryChanges.Patch.Length); + Assert.Equal("numbers.txt", entryChanges.Path); + } } } @@ -866,45 +907,47 @@ public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks(int contextLines, in Similarity = SimilarityOptions.None, }; - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree mergedCommitTree = repo.Lookup("7252fe2").Tree; - var changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions); - - Assert.Equal(3, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(1, changes.Added.Count()); - - Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Mode); - - var patch = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions); + using (var changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions)) + { + Assert.Equal(3, changes.Count()); + Assert.Single(changes.Modified); + Assert.Single(changes.Deleted); + Assert.Single(changes.Added); - PatchEntryChanges entryChanges = patch["numbers.txt"]; + Assert.Equal(Mode.Nonexistent, changes.Single(c => c.Path == "my-name-does-not-feel-right.txt").Mode); + } - Assert.Equal(3, entryChanges.LinesAdded); - Assert.Equal(1, entryChanges.LinesDeleted); - Assert.Equal(Expected("f8d44d7...7252fe2/numbers.txt-{0}-{1}.diff", contextLines, interhunkLines), - entryChanges.Patch); - Assert.Equal(Expected("f8d44d7...7252fe2/full-{0}-{1}.diff", contextLines, interhunkLines), - patch); - Assert.Equal("numbers.txt", entryChanges.Path); + using (var patch = repo.Diff.Compare(rootCommitTree, mergedCommitTree, compareOptions: compareOptions)) + { + PatchEntryChanges entryChanges = patch["numbers.txt"]; + + Assert.Equal(3, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(Expected("f8d44d7...7252fe2/numbers.txt-{0}-{1}.diff", contextLines, interhunkLines), + entryChanges.Patch); + Assert.Equal(Expected("f8d44d7...7252fe2/full-{0}-{1}.diff", contextLines, interhunkLines), + patch); + Assert.Equal("numbers.txt", entryChanges.Path); + } } } - [Fact] - public void CanHandleTwoTreeEntryChangesWithTheSamePath() + private void CanHandleTwoTreeEntryChangesWithTheSamePath(SimilarityOptions similarity, Action verifier) { string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { - Blob mainContent = OdbHelper.CreateBlob(repo, "awesome content\n"); + Blob mainContent = OdbHelper.CreateBlob(repo, "awesome content\n" + new string('b', 4096)); Blob linkContent = OdbHelper.CreateBlob(repo, "../../objc/Nu.h"); - string path = string.Format("include{0}Nu{0}Nu.h", Path.DirectorySeparatorChar); + string path = Path.Combine("include", "Nu", "Nu.h"); var tdOld = new TreeDefinition() .Add(path, linkContent, Mode.SymbolicLink) @@ -917,81 +960,103 @@ public void CanHandleTwoTreeEntryChangesWithTheSamePath() Tree treeNew = repo.ObjectDatabase.CreateTree(tdNew); - var changes = repo.Diff.Compare(treeOld, treeNew, + using (var changes = repo.Diff.Compare(treeOld, treeNew, compareOptions: new CompareOptions { - Similarity = SimilarityOptions.None, - }); - - /* - * $ git diff-tree -p 5c87b67 d5278d0 - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * deleted file mode 120000 - * index 19bf568..0000000 - * --- a/include/Nu/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -../../objc/Nu.h - * \ No newline at end of file - * diff --git a/include/Nu/Nu.h b/include/Nu/Nu.h - * new file mode 100644 - * index 0000000..f9e6561 - * --- /dev/null - * +++ b/include/Nu/Nu.h - * @@ -0,0 +1 @@ - * +awesome content - * diff --git a/objc/Nu.h b/objc/Nu.h - * deleted file mode 100644 - * index f9e6561..0000000 - * --- a/objc/Nu.h - * +++ /dev/null - * @@ -1 +0,0 @@ - * -awesome content - */ - - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(0, changes.Modified.Count()); - Assert.Equal(1, changes.TypeChanged.Count()); - - TreeEntryChanges change = changes[path]; - Assert.Equal(Mode.SymbolicLink, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); - Assert.Equal(ChangeKind.TypeChanged, change.Status); - Assert.Equal(path, change.Path); + Similarity = similarity, + })) + { + verifier(path, changes); + } } } + [Fact] + public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityNone() + { + // $ git diff-tree --name-status --no-renames -r 2ccccf8 e829333 + // T include/Nu/Nu.h + // D objc/Nu.h + + CanHandleTwoTreeEntryChangesWithTheSamePath(SimilarityOptions.None, + (path, changes) => + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Deleted); + Assert.Single(changes.TypeChanged); + + TreeEntryChanges change = changes.Single(c => c.Path == path); + Assert.Equal(Mode.SymbolicLink, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + Assert.Equal(ChangeKind.TypeChanged, change.Status); + Assert.Equal(path, change.Path); + }); + } + + [Fact] + public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityDefault() + { + // $ git diff-tree --name-status --find-renames -r 2ccccf8 e829333 + // T include/Nu/Nu.h + // D objc/Nu.h + + CanHandleTwoTreeEntryChangesWithTheSamePath(SimilarityOptions.Default, + (path, changes) => + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Deleted); + Assert.Single(changes.Renamed); + + TreeEntryChanges renamed = changes.Renamed.Single(); + Assert.Equal(Mode.NonExecutableFile, renamed.OldMode); + Assert.Equal(Mode.NonExecutableFile, renamed.Mode); + Assert.Equal(ChangeKind.Renamed, renamed.Status); + Assert.Equal(path, renamed.Path); + + TreeEntryChanges deleted = changes.Deleted.Single(); + Assert.Equal(Mode.SymbolicLink, deleted.OldMode); + Assert.Equal(Mode.Nonexistent, deleted.Mode); + Assert.Equal(ChangeKind.Deleted, deleted.Status); + Assert.Equal(path, deleted.Path); + }); + } + [Fact] public void CanCompareATreeAgainstANullTree() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree tree = repo.Branches["refs/remotes/origin/test"].Tip.Tree; - var changes = repo.Diff.Compare(tree, null); - - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Deleted.Count()); - - Assert.Equal("readme.txt", changes.Deleted.Single().Path); + using (var changes = repo.Diff.Compare(tree, null)) + { + Assert.Single(changes); + Assert.Single(changes.Deleted); - changes = repo.Diff.Compare(null, tree); + Assert.Equal("readme.txt", changes.Deleted.Single().Path); + } - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + using (var changes = repo.Diff.Compare(null, tree)) + { + Assert.Single(changes); + Assert.Single(changes.Added); - Assert.Equal("readme.txt", changes.Added.Single().Path); + Assert.Equal("readme.txt", changes.Added.Single().Path); + } } } [Fact] public void ComparingTwoNullTreesReturnsAnEmptyTreeChanges() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(default(Tree), default(Tree)); - - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(default(Tree), default(Tree))) + { + Assert.Empty(changes); + } } } @@ -1000,7 +1065,7 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() { const string file = "1/branch_file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { TreeEntry entry = repo.Head[file]; @@ -1018,53 +1083,71 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() repo.Config.Unset("core.filemode"); } - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + using (var repo = new Repository(path)) + { + SetFilemode(repo, true); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Single(changes); - var options = BuildFakeSystemConfigFilemodeOption(scd, true); + var change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } + } - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); + SetFilemode(repo, false); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } + } + } - Assert.Equal(1, changes.Count()); + void SetFilemode(Repository repo, bool value) + { + repo.Config.Set("core.filemode", value); + } - var change = changes.Modified.Single(); - Assert.Equal(Mode.ExecutableFile, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); - } + [Fact] + public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() + { + ObjectId treeOldOid, treeNewOid; - options = BuildFakeSystemConfigFilemodeOption(scd, false); + string repoPath = InitNewRepository(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(repoPath)) { - var changes = repo.Diff.Compare(new[] { file }); + Blob oldContent = OdbHelper.CreateBlob(repo, "awesome content\n"); + Blob newContent = OdbHelper.CreateBlob(repo, "more awesome content\n"); - Assert.Equal(0, changes.Count()); - } - } + var td = new TreeDefinition() + .Add("A.TXT", oldContent, Mode.NonExecutableFile) + .Add("a.txt", oldContent, Mode.NonExecutableFile); - private RepositoryOptions BuildFakeSystemConfigFilemodeOption( - SelfCleaningDirectory scd, - bool value) - { - Directory.CreateDirectory(scd.DirectoryPath); + treeOldOid = repo.ObjectDatabase.CreateTree(td).Id; - var options = new RepositoryOptions - { - SystemConfigurationLocation = Path.Combine( - scd.RootedDirectoryPath, "fake-system.config") - }; + td = new TreeDefinition() + .Add("A.TXT", newContent, Mode.NonExecutableFile) + .Add("a.txt", newContent, Mode.NonExecutableFile); - StringBuilder sb = new StringBuilder() - .AppendFormat("[core]{0}", Environment.NewLine) - .AppendFormat("filemode = {1}{0}", Environment.NewLine, value); - Touch("", options.SystemConfigurationLocation, sb.ToString()); + treeNewOid = repo.ObjectDatabase.CreateTree(td).Id; + } - return options; + using (var repo = new Repository(repoPath)) + { + using (var changes = repo.Diff.Compare(repo.Lookup(treeOldOid), repo.Lookup(treeNewOid))) + { + Assert.Equal(ChangeKind.Modified, changes.Single(c => c.Path == "a.txt").Status); + Assert.Equal(ChangeKind.Modified, changes.Single(c => c.Path == "A.TXT").Status); + } + } } [Fact] - public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() + public void RetrievingDiffContainsRightAmountOfAddedAndDeletedLines() { ObjectId treeOldOid, treeNewOid; @@ -1090,20 +1173,122 @@ public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() using (var repo = new Repository(repoPath)) { - var changes = repo.Diff.Compare(repo.Lookup(treeOldOid), repo.Lookup(treeNewOid)); + using (var changes = repo.Diff.Compare(repo.Lookup(treeOldOid), repo.Lookup(treeNewOid))) + { + foreach (var entry in changes) + { + Assert.Single(entry.AddedLines); + Assert.Single(entry.DeletedLines); + } + } + } + } - Assert.Equal(ChangeKind.Modified, changes["a.txt"].Status); - Assert.Equal(ChangeKind.Modified, changes["A.TXT"].Status); + [Fact] + public void UsingPatienceAlgorithmCompareOptionProducesPatienceDiff() + { + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + Func fromString = + s => + repo.ObjectDatabase.CreateTree(new TreeDefinition().Add("file.txt", + OdbHelper.CreateBlob(repo, s), Mode.NonExecutableFile)); + + Tree treeOld = fromString(new StringBuilder() + .Append("aaaaaa\n") + .Append("aaaaaa\n") + .Append("bbbbbb\n") + .Append("bbbbbb\n") + .Append("cccccc\n") + .Append("cccccc\n") + .Append("abc\n").ToString()); + + Tree treeNew = fromString(new StringBuilder() + .Append("abc\n") + .Append("aaaaaa\n") + .Append("aaaaaa\n") + .Append("bbbbbb\n") + .Append("bbbbbb\n") + .Append("cccccc\n") + .Append("cccccc\n").ToString()); + + string diffDefault = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index 3299d68..accc3bd 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1,7 +1,7 @@\n") + .Append("+abc\n") + .Append(" aaaaaa\n") + .Append(" aaaaaa\n") + .Append(" bbbbbb\n") + .Append(" bbbbbb\n") + .Append(" cccccc\n") + .Append(" cccccc\n") + .Append("-abc\n").ToString(); + + string diffPatience = new StringBuilder() + .Append("diff --git a/file.txt b/file.txt\n") + .Append("index 3299d68..accc3bd 100644\n") + .Append("--- a/file.txt\n") + .Append("+++ b/file.txt\n") + .Append("@@ -1,7 +1,7 @@\n") + .Append("-aaaaaa\n") + .Append("-aaaaaa\n") + .Append("-bbbbbb\n") + .Append("-bbbbbb\n") + .Append("-cccccc\n") + .Append("-cccccc\n") + .Append(" abc\n") + .Append("+aaaaaa\n") + .Append("+aaaaaa\n") + .Append("+bbbbbb\n") + .Append("+bbbbbb\n") + .Append("+cccccc\n") + .Append("+cccccc\n").ToString(); + + using (var changes = repo.Diff.Compare(treeOld, treeNew)) + Assert.Equal(diffDefault, changes); + + using (var changes = repo.Diff.Compare(treeOld, treeNew, + compareOptions: new CompareOptions { Algorithm = DiffAlgorithm.Patience })) + Assert.Equal(diffPatience, changes); } } [Fact] - public void CallingCompareWithAnUnsupportedGenericParamThrows() + public void DiffThrowsANotFoundExceptionIfATreeIsMissing() { - using (var repo = new Repository(StandardTestRepoPath)) + string repoPath = SandboxBareTestRepo(); + + // Manually delete the tree object to simulate a partial clone + File.Delete(Path.Combine(repoPath, "objects", "58", "1f9824ecaf824221bd36edf5430f2739a7c4f5")); + + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.Diff.Compare(default(Tree), default(Tree))); - Assert.Throws(() => repo.Diff.Compare()); + // The commit is there but its tree is missing + var commit = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + Assert.NotNull(commit); + Assert.Equal("581f9824ecaf824221bd36edf5430f2739a7c4f5", commit.Tree.Sha); + Assert.True(commit.Tree.IsMissing); + + var tree = repo.Lookup("581f9824ecaf824221bd36edf5430f2739a7c4f5"); + Assert.Null(tree); + + var otherCommit = repo.Lookup("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + Assert.NotNull(otherCommit); + Assert.False(otherCommit.Tree.IsMissing); + + Assert.Throws(() => + { + using (repo.Diff.Compare(commit.Tree, otherCommit.Tree)) { } + }); + + Assert.Throws(() => + { + using (repo.Diff.Compare(otherCommit.Tree, commit.Tree)) { } + }); } } } diff --git a/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs b/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs index 2b7c528c4..c6ef700bb 100644 --- a/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs +++ b/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs @@ -30,63 +30,74 @@ public class DiffWorkdirToIndexFixture : BaseFixture [Fact] public void CanCompareTheWorkDirAgainstTheIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(); - - Assert.Equal(2, changes.Count()); - Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + using (var changes = repo.Diff.Compare()) + { + Assert.Equal(2, changes.Count()); + Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + } } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("really-i-cant-exist.txt", FileStatus.Nonexistent)] public void CanCompareTheWorkDirAgainstTheIndexWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } - changes = repo.Diff.Compare(new[] { relativePath }); - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(new[] { relativePath })) + { + Assert.Empty(changes); + } } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("really-i-cant-exist.txt", FileStatus.Nonexistent)] public void ComparingTheWorkDirAgainstTheIndexWithStrictUnmatchedExplicitPathsValidationAndANonExistentPathspecThrows(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); Assert.Throws(() => repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions())); } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("where-am-I.txt", FileStatus.Nonexistent)] public void CallbackForUnmatchedExplicitPathsIsCalledWhenSet(string relativePath, FileStatus currentStatus) { var callback = new AssertUnmatchedPathspecsCallbackIsCalled(); - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false, - OnUnmatchedPath = callback.OnUnmatchedPath }); - - Assert.True(callback.WasCalled); + OnUnmatchedPath = callback.OnUnmatchedPath + })) + { + Assert.True(callback.WasCalled); + } } } @@ -105,7 +116,7 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() { const string file = "1/branch_file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { TreeEntry entry = repo.Head[file]; @@ -123,62 +134,47 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() repo.Config.Unset("core.filemode", ConfigurationLevel.Local); } - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeSystemConfigFilemodeOption(scd, true); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(1, changes.Count()); + SetFilemode(repo, true); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Single(changes); - var change = changes.Modified.Single(); - Assert.Equal(Mode.ExecutableFile, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); + var change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } } - options = BuildFakeSystemConfigFilemodeOption(scd, false); - - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(new[] { file }); - - Assert.Equal(0, changes.Count()); + SetFilemode(repo, false); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } } } - private RepositoryOptions BuildFakeSystemConfigFilemodeOption( - SelfCleaningDirectory scd, - bool value) + void SetFilemode(Repository repo, bool value) { - Directory.CreateDirectory(scd.DirectoryPath); - - var options = new RepositoryOptions - { - SystemConfigurationLocation = Path.Combine( - scd.RootedDirectoryPath, "fake-system.config") - }; - - StringBuilder sb = new StringBuilder() - .AppendFormat("[core]{0}", Environment.NewLine) - .AppendFormat("filemode = {1}{0}", Environment.NewLine, value); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); - - return options; + repo.Config.Set("core.filemode", value); } [Fact] public void CanCompareTheWorkDirAgainstTheIndexWithUntrackedFiles() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - var changes = repo.Diff.Compare(null, true); - - Assert.Equal(3, changes.Count()); - Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); - Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); - Assert.Equal("new_untracked_file.txt", changes.Added.Single().Path); + using (var changes = repo.Diff.Compare(null, true)) + { + Assert.Equal(3, changes.Count()); + Assert.Equal("deleted_unstaged_file.txt", changes.Deleted.Single().Path); + Assert.Equal("modified_unstaged_file.txt", changes.Modified.Single().Path); + Assert.Equal("new_untracked_file.txt", changes.Added.Single().Path); + } } } } diff --git a/LibGit2Sharp.Tests/EpochFixture.cs b/LibGit2Sharp.Tests/EpochFixture.cs deleted file mode 100644 index 773f4c846..000000000 --- a/LibGit2Sharp.Tests/EpochFixture.cs +++ /dev/null @@ -1,87 +0,0 @@ -using System; -using LibGit2Sharp.Core; -using Xunit; -using Xunit.Extensions; - -namespace LibGit2Sharp.Tests -{ - public class EpochFixture - { - [Theory] - [InlineData(0)] - [InlineData(17)] - public void UnixTimestampShouldBeCastIntoAUtcBasedDateTimeOffset(long secondsSinceEpoch) - { - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, 0); - Assert.Equal(0, date.Offset.TotalMinutes); - - Assert.Equal(TimeSpan.Zero, date.Offset); - Assert.Equal(DateTimeKind.Utc, date.UtcDateTime.Kind); - } - - [Theory] - [InlineData(0, 0)] - [InlineData(17, -120)] - [InlineData(31, 60)] - public void AreEqual(long secondsSinceEpoch, int timezoneOffset) - { - DateTimeOffset one = Epoch.ToDateTimeOffset(secondsSinceEpoch, timezoneOffset); - DateTimeOffset another = Epoch.ToDateTimeOffset(secondsSinceEpoch, timezoneOffset); - - Assert.Equal(one, another); - Assert.Equal(another, one); - - Assert.True(one == another); - Assert.True(another == one); - - Assert.False(one != another); - Assert.False(another != one); - - Assert.Equal(one.GetHashCode(), another.GetHashCode()); - } - - [Theory] - [InlineData(1291801952, "Wed, 08 Dec 2010 09:52:32 +0000")] - [InlineData(1234567890, "Fri, 13 Feb 2009 23:31:30 +0000")] - [InlineData(1288114383, "Tue, 26 Oct 2010 17:33:03 +0000")] - public void UnixTimestampShouldShouldBeCastIntoAPlainUtcDate(long secondsSinceEpoch, string expected) - { - DateTimeOffset expectedDate = DateTimeOffset.Parse(expected); - - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, 0); - - Assert.Equal(secondsSinceEpoch, date.ToSecondsSinceEpoch()); - Assert.Equal(expectedDate, date); - Assert.Equal(TimeSpan.Zero, date.Offset); - } - - [Theory] - [InlineData(1250379778, -210, "Sat, 15 Aug 2009 20:12:58 -0330")] - public void UnixTimestampAndTimezoneOffsetShouldBeCastIntoAUtcDateBearingAnOffset(long secondsSinceEpoch, Int32 offset, string expected) - { - DateTimeOffset expectedDate = DateTimeOffset.Parse(expected); - - DateTimeOffset date = Epoch.ToDateTimeOffset(secondsSinceEpoch, offset); - Assert.Equal(offset, date.Offset.TotalMinutes); - Assert.Equal(secondsSinceEpoch, date.ToSecondsSinceEpoch()); - - Assert.Equal(expectedDate, date); - Assert.Equal(expectedDate.Offset, date.Offset); - } - - [Theory] - [InlineData("Wed, 08 Dec 2010 09:52:32 +0000", 1291801952, 0)] - [InlineData("Fri, 13 Feb 2009 23:31:30 +0000", 1234567890, 0)] - [InlineData("Tue, 26 Oct 2010 17:33:03 +0000", 1288114383, 0)] - [InlineData("Sat, 14 Feb 2009 00:31:30 +0100", 1234567890, 60)] - [InlineData("Sat, 15 Aug 2009 20:12:58 -0330", 1250379778, -210)] - [InlineData("Sat, 15 Aug 2009 23:42:58 +0000", 1250379778, 0)] - [InlineData("Sun, 16 Aug 2009 00:42:58 +0100", 1250379778, 60)] - public void DateTimeOffsetShoudlBeCastIntoAUnixTimestampAndATimezoneOffset(string formattedDate, long expectedSeconds, Int32 expectedOffset) - { - DateTimeOffset when = DateTimeOffset.Parse(formattedDate); - DateTimeOffset date = Epoch.ToDateTimeOffset(expectedSeconds, expectedOffset); - Assert.Equal(when, date); - } - } -} diff --git a/LibGit2Sharp.Tests/FetchFixture.cs b/LibGit2Sharp.Tests/FetchFixture.cs index b9a4048a7..b36da7ccd 100644 --- a/LibGit2Sharp.Tests/FetchFixture.cs +++ b/LibGit2Sharp.Tests/FetchFixture.cs @@ -1,8 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -10,15 +11,16 @@ public class FetchFixture : BaseFixture { private const string remoteName = "testRemote"; - [Theory(Skip = "Skipping due to recent github handling modification of --include-tag.")] + [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository.git")] public void CanFetchIntoAnEmptyRepository(string url) { - using (var repo = InitIsolatedRepository()) + string path = InitNewRepository(); + + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. @@ -40,7 +42,7 @@ public void CanFetchIntoAnEmptyRepository(string url) } // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }); + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); @@ -53,62 +55,73 @@ public void CanFetchIntoAnEmptyRepositoryWithCredentials() InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), "Populate Constants.PrivateRepo* to run this test"); - using (var repo = InitIsolatedRepository()) + string path = InitNewRepository(); + + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); + repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions { CredentialsProvider = Constants.PrivateRepoCredentials - }); + }, null); } } [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository.git")] public void CanFetchAllTagsIntoAnEmptyRepository(string url) { - using (var repo = InitIsolatedRepository()) + string path = InitNewRepository(); + + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. TestRemoteInfo remoteInfo = TestRemoteInfo.TestRemoteInstance; var expectedFetchState = new ExpectedFetchState(remoteName); - // Add expected tags only as no branches are expected to be fetched + // Add expected tags foreach (KeyValuePair kvp in remoteInfo.Tags) { expectedFetchState.AddExpectedTag(kvp.Key, ObjectId.Zero, kvp.Value); } + // Add expected branch objects + foreach (KeyValuePair kvp in remoteInfo.BranchTips) + { + expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value); + } + // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions + { TagFetchMode = TagFetchMode.All, OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler - }); + }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); // Verify the reflog entries - Assert.Equal(0, repo.Refs.Log(string.Format("refs/remotes/{0}/master", remoteName)).Count()); // Only tags are retrieved + Assert.Single(repo.Refs.Log(string.Format("refs/remotes/{0}/master", remoteName))); // Branches are also retrieved } } [Theory] [InlineData("http://github.com/libgit2/TestGitRepository", "test-branch", "master")] [InlineData("https://github.com/libgit2/TestGitRepository", "master", "master")] - [InlineData("git://github.com/libgit2/TestGitRepository.git", "master", "first-merge")] public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string localBranchName, string remoteBranchName) { - using (var repo = InitIsolatedRepository()) + string path = InitNewRepository(); + + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); string refSpec = string.Format("refs/heads/{2}:refs/remotes/{0}/{1}", remoteName, localBranchName, remoteBranchName); @@ -118,22 +131,35 @@ public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string local var expectedFetchState = new ExpectedFetchState(remoteName); expectedFetchState.AddExpectedBranch(localBranchName, ObjectId.Zero, remoteInfo.BranchTips[remoteBranchName]); + // Let's account for opportunistic updates during the Fetch() call + if (!string.Equals("master", localBranchName, StringComparison.OrdinalIgnoreCase)) + { + expectedFetchState.AddExpectedBranch("master", ObjectId.Zero, remoteInfo.BranchTips["master"]); + } + + if (string.Equals("master", localBranchName, StringComparison.OrdinalIgnoreCase) + && !string.Equals("master", remoteBranchName, StringComparison.OrdinalIgnoreCase)) + { + expectedFetchState.AddExpectedBranch(remoteBranchName, ObjectId.Zero, remoteInfo.BranchTips[remoteBranchName]); + } + // Perform the actual fetch - repo.Network.Fetch(remote, new string[] { refSpec }, new FetchOptions { + Commands.Fetch(repo, remoteName, new string[] { refSpec }, new FetchOptions + { TagFetchMode = TagFetchMode.None, OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler - }); + }, null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); // Verify the reflog entries var reflogEntry = repo.Refs.Log(string.Format("refs/remotes/{0}/{1}", remoteName, localBranchName)).Single(); - Assert.True(reflogEntry.Message.StartsWith("fetch ")); + Assert.StartsWith("fetch ", reflogEntry.Message); } } - [Theory(Skip = "Skipping due to recent github handling modification of --include-tag.")] + [Theory] [InlineData(TagFetchMode.All, 4)] [InlineData(TagFetchMode.None, 0)] [InlineData(TagFetchMode.Auto, 3)] @@ -141,21 +167,127 @@ public void FetchRespectsConfiguredAutoTagSetting(TagFetchMode tagFetchMode, int { string url = "http://github.com/libgit2/TestGitRepository"; - using (var repo = InitIsolatedRepository()) + string path = InitNewRepository(); + + using (var repo = new Repository(path)) { Remote remote = repo.Network.Remotes.Add(remoteName, url); Assert.NotNull(remote); // Update the configured autotag setting. - repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update(remoteName, r => r.TagFetchMode = tagFetchMode); // Perform the actual fetch. - repo.Network.Fetch(remote); + Commands.Fetch(repo, remoteName, Array.Empty(), null, null); // Verify the number of fetched tags. Assert.Equal(expectedTagCount, repo.Tags.Count()); } } + + [Fact] + public void CanFetchAllTagsAfterAnInitialClone() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + using (var repo = new Repository(clonedRepoPath)) + { + Commands.Fetch(repo, "origin", Array.Empty(), new FetchOptions { TagFetchMode = TagFetchMode.All }, null); + } + } + + [Fact] + public void FetchHonorsTheFetchPruneConfigurationEntry() + { + var source = SandboxBareTestRepo(); + var url = new Uri($"file://{Path.GetFullPath(source)}").AbsoluteUri; + + var scd = BuildSelfCleaningDirectory(); + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + using (var clonedRepo = new Repository(clonedRepoPath)) + { + Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote && b.FriendlyName != "origin/HEAD")); + + // Drop one of the branches in the remote repository + using (var sourceRepo = new Repository(source)) + { + sourceRepo.Branches.Remove("packed-test"); + } + + // No pruning when the configuration entry isn't defined + Assert.Null(clonedRepo.Config.Get("fetch.prune")); + Commands.Fetch(clonedRepo, "origin", Array.Empty(), null, null); + Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote && b.FriendlyName != "origin/HEAD")); + + // No pruning when the configuration entry is set to false + clonedRepo.Config.Set("fetch.prune", false); + Commands.Fetch(clonedRepo, "origin", Array.Empty(), null, null); + Assert.Equal(5, clonedRepo.Branches.Count(b => b.IsRemote && b.FriendlyName != "origin/HEAD")); + + // Auto pruning when the configuration entry is set to true + clonedRepo.Config.Set("fetch.prune", true); + Commands.Fetch(clonedRepo, "origin", Array.Empty(), null, null); + Assert.Equal(4, clonedRepo.Branches.Count(b => b.IsRemote && b.FriendlyName != "origin/HEAD")); + } + } + + [Fact] + public void CannotFetchWithForbiddenCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + const string knownHeader = "User-Agent: mygit-201"; + var options = new FetchOptions { CustomHeaders = new string[] { knownHeader } }; + using (var repo = new Repository(clonedRepoPath)) + { + Assert.Throws(() => Commands.Fetch(repo, "origin", Array.Empty(), options, null)); + } + } + + [Fact] + public void CanFetchWithCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + const string knownHeader = "X-Hello: mygit-201"; + var options = new FetchOptions { CustomHeaders = new string[] { knownHeader } }; + using (var repo = new Repository(clonedRepoPath)) + { + Commands.Fetch(repo, "origin", Array.Empty(), options, null); + } + } + + [Fact] + public void CannotFetchWithMalformedCustomHeaders() + { + var scd = BuildSelfCleaningDirectory(); + + const string url = "https://github.com/libgit2/TestGitRepository"; + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + const string knownHeader = "Hello world"; + var options = new FetchOptions { CustomHeaders = new string[] { knownHeader } }; + using (var repo = new Repository(clonedRepoPath)) + { + Assert.Throws(() => Commands.Fetch(repo, "origin", Array.Empty(), options, null)); + } + } + } } diff --git a/LibGit2Sharp.Tests/FileHistoryFixture.cs b/LibGit2Sharp.Tests/FileHistoryFixture.cs new file mode 100644 index 000000000..dcbd0e6d8 --- /dev/null +++ b/LibGit2Sharp.Tests/FileHistoryFixture.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class FileHistoryFixture : BaseFixture + { + //Looks like nulltoken deleted the repo this test was using + + //[Theory] + //[InlineData("https://github.com/nulltoken/follow-test.git")] + //public void CanDealWithFollowTest(string url) + //{ + // var scd = BuildSelfCleaningDirectory(); + // var clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + // using (var repo = new Repository(clonedRepoPath)) + // { + // // $ git log --follow --format=oneline so-renamed.txt + // // 88f91835062161febb46fb270ef4188f54c09767 Update not-yet-renamed.txt AND rename into so-renamed.txt + // // ef7cb6a63e32595fffb092cb1ae9a32310e58850 Add not-yet-renamed.txt + // var fileHistoryEntries = repo.Commits.QueryBy("so-renamed.txt").ToList(); + // Assert.Equal(2, fileHistoryEntries.Count()); + // Assert.Equal("88f91835062161febb46fb270ef4188f54c09767", fileHistoryEntries[0].Commit.Sha); + // Assert.Equal("ef7cb6a63e32595fffb092cb1ae9a32310e58850", fileHistoryEntries[1].Commit.Sha); + + // // $ git log --follow --format=oneline untouched.txt + // // c10c1d5f74b76f20386d18674bf63fbee6995061 Initial commit + // fileHistoryEntries = repo.Commits.QueryBy("untouched.txt").ToList(); + // Assert.Single(fileHistoryEntries); + // Assert.Equal("c10c1d5f74b76f20386d18674bf63fbee6995061", fileHistoryEntries[0].Commit.Sha); + + // // $ git log --follow --format=oneline under-test.txt + // // 0b5b18f2feb917dee98df1210315b2b2b23c5bec Rename file renamed.txt into under-test.txt + // // 49921d463420a892c9547a326632ef6a9ba3b225 Update file renamed.txt + // // 70f636e8c64bbc2dfef3735a562bb7e195d8019f Rename file under-test.txt into renamed.txt + // // d3868d57a6aaf2ae6ed4887d805ae4bc91d8ce4d Updated file under test + // // 9da10ef7e139c49604a12caa866aae141f38b861 Updated file under test + // // 599a5d821fb2c0a25855b4233e26d475c2fbeb34 Updated file under test + // // 678b086b44753000567aa64344aa0d8034fa0083 Updated file under test + // // 8f7d9520f306771340a7c79faea019ad18e4fa1f Updated file under test + // // bd5f8ee279924d33be8ccbde82e7f10b9d9ff237 Updated file under test + // // c10c1d5f74b76f20386d18674bf63fbee6995061 Initial commit + // fileHistoryEntries = repo.Commits.QueryBy("under-test.txt").ToList(); + // Assert.Equal(10, fileHistoryEntries.Count()); + // Assert.Equal("0b5b18f2feb917dee98df1210315b2b2b23c5bec", fileHistoryEntries[0].Commit.Sha); + // Assert.Equal("49921d463420a892c9547a326632ef6a9ba3b225", fileHistoryEntries[1].Commit.Sha); + // Assert.Equal("70f636e8c64bbc2dfef3735a562bb7e195d8019f", fileHistoryEntries[2].Commit.Sha); + // Assert.Equal("d3868d57a6aaf2ae6ed4887d805ae4bc91d8ce4d", fileHistoryEntries[3].Commit.Sha); + // Assert.Equal("9da10ef7e139c49604a12caa866aae141f38b861", fileHistoryEntries[4].Commit.Sha); + // Assert.Equal("599a5d821fb2c0a25855b4233e26d475c2fbeb34", fileHistoryEntries[5].Commit.Sha); + // Assert.Equal("678b086b44753000567aa64344aa0d8034fa0083", fileHistoryEntries[6].Commit.Sha); + // Assert.Equal("8f7d9520f306771340a7c79faea019ad18e4fa1f", fileHistoryEntries[7].Commit.Sha); + // Assert.Equal("bd5f8ee279924d33be8ccbde82e7f10b9d9ff237", fileHistoryEntries[8].Commit.Sha); + // Assert.Equal("c10c1d5f74b76f20386d18674bf63fbee6995061", fileHistoryEntries[9].Commit.Sha); + // } + //} + + [Theory] + [InlineData(null)] + public void CanFollowBranches(string specificRepoPath) + { + var repoPath = specificRepoPath ?? CreateEmptyRepository(); + var path = "Test.txt"; + + var dummy = new string('a', 1024); + + using (var repo = new Repository(repoPath)) + { + var master0 = AddCommitToOdb(repo, "0. Initial commit for this test", path, "Before merge", dummy); + var fix1 = AddCommitToOdb(repo, "1. Changed on fix", path, "Change on fix branch", dummy, master0); + var master2 = AddCommitToOdb(repo, "2. Changed on master", path, "Independent change on master branch", + dummy, master0); + + path = "New" + path; + + var fix3 = AddCommitToOdb(repo, "3. Changed and renamed on fix", path, "Another change on fix branch", + dummy, fix1); + var master4 = AddCommitToOdb(repo, "4. Changed and renamed on master", path, + "Another independent change on master branch", dummy, master2); + var master5 = AddCommitToOdb(repo, "5. Merged fix into master", path, + "Manual resolution of merge conflict", dummy, master4, fix3); + var master6 = AddCommitToOdb(repo, "6. Changed on master", path, "Change after merge", dummy, master5); + var nextfix7 = AddCommitToOdb(repo, "7. Changed on next-fix", path, "Change on next-fix branch", dummy, + master6); + var master8 = AddCommitToOdb(repo, "8. Changed on master", path, + "Some arbitrary change on master branch", dummy, master6); + var master9 = AddCommitToOdb(repo, "9. Merged next-fix into master", path, + "Another manual resolution of merge conflict", dummy, master8, nextfix7); + var master10 = AddCommitToOdb(repo, "10. Changed on master", path, "A change on master after merging", + dummy, master9); + + repo.CreateBranch("master", master10); + Commands.Checkout(repo, "master", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + + // Test --date-order. + var timeHistory = repo.Commits.QueryBy(path, + new CommitFilter { SortBy = CommitSortStrategies.Time }); + var timeCommits = new List + { + master10, // master + + master8, // master + nextfix7, // next-fix + master6, // master + + master4, // master + fix3, // fix + master2, // master + fix1, // fix + master0 // master (initial commit) + }; + Assert.Equal(timeCommits, timeHistory.Select(e => e.Commit)); + + // Test --topo-order. + var topoHistory = repo.Commits.QueryBy(path, + new CommitFilter { SortBy = CommitSortStrategies.Topological }); + var topoCommits = new List + { + master10, // master + + nextfix7, // next-fix + master8, // master + master6, // master + + fix3, // fix + fix1, // fix + master4, // master + master2, // master + master0 // master (initial commit) + }; + Assert.Equal(topoCommits, topoHistory.Select(e => e.Commit)); + } + } + + [Fact] + public void CanTellComplexCommitHistory() + { + var repoPath = CreateEmptyRepository(); + const string path1 = "Test1.txt"; + const string path2 = "Test2.txt"; + + using (var repo = new Repository(repoPath)) + { + // Make initial changes. + var commit1 = MakeAndCommitChange(repo, repoPath, path1, "Hello World"); + MakeAndCommitChange(repo, repoPath, path2, "Second file's contents"); + var commit2 = MakeAndCommitChange(repo, repoPath, path1, "Hello World again"); + + // Move the first file to a new directory. + var newPath1 = Path.Combine(SubFolderPath1, path1).Replace(@"\", "/"); + Commands.Move(repo, path1, newPath1); + var commit3 = repo.Commit("Moved " + path1 + " to " + newPath1, + Constants.Signature, Constants.Signature); + + // Make further changes. + MakeAndCommitChange(repo, repoPath, path2, "Changed second file's contents"); + var commit4 = MakeAndCommitChange(repo, repoPath, newPath1, "I have done it again!"); + + // Perform tests. + var commitFilter = new CommitFilter() { SortBy = CommitSortStrategies.Topological }; + var fileHistoryEntries = repo.Commits.QueryBy(newPath1, commitFilter).ToList(); + var changedBlobs = fileHistoryEntries.Blobs().Distinct().ToList(); + + Assert.Equal(4, fileHistoryEntries.Count()); + Assert.Equal(3, changedBlobs.Count()); + + Assert.Equal(2, fileHistoryEntries.Count(e => e.Path == newPath1)); + Assert.Equal(2, fileHistoryEntries.Count(e => e.Path == path1)); + + Assert.Equal(commit4, fileHistoryEntries[0].Commit); + Assert.Equal(commit3, fileHistoryEntries[1].Commit); + Assert.Equal(commit2, fileHistoryEntries[2].Commit); + Assert.Equal(commit1, fileHistoryEntries[3].Commit); + + Assert.Equal(commit4.Tree[newPath1].Target, changedBlobs[0]); + Assert.Equal(commit2.Tree[path1].Target, changedBlobs[1]); + Assert.Equal(commit1.Tree[path1].Target, changedBlobs[2]); + } + } + + [Fact] + public void CanTellSimpleCommitHistory() + { + var repoPath = CreateEmptyRepository(); + const string path1 = "Test1.txt"; + const string path2 = "Test2.txt"; + + using (var repo = new Repository(repoPath)) + { + // Set up repository. + var commit1 = MakeAndCommitChange(repo, repoPath, path1, "Hello World"); + MakeAndCommitChange(repo, repoPath, path2, "Second file's contents"); + var commit3 = MakeAndCommitChange(repo, repoPath, path1, "Hello World again"); + + // Perform tests. + IEnumerable history = repo.Commits.QueryBy(path1).ToList(); + var changedBlobs = history.Blobs().Distinct(); + + Assert.Equal(2, history.Count()); + Assert.Equal(2, changedBlobs.Count()); + + Assert.Equal(commit3, history.ElementAt(0).Commit); + Assert.Equal(commit1, history.ElementAt(1).Commit); + } + } + + [Fact] + public void CanTellSingleCommitHistory() + { + var repoPath = CreateEmptyRepository(); + + using (var repo = new Repository(repoPath)) + { + // Set up repository. + const string path = "Test.txt"; + var commit = MakeAndCommitChange(repo, repoPath, path, "Hello World"); + + // Perform tests. + IEnumerable history = repo.Commits.QueryBy(path).ToList(); + var changedBlobs = history.Blobs().Distinct(); + + Assert.Single(history); + Assert.Single(changedBlobs); + + Assert.Equal(path, history.First().Path); + Assert.Equal(commit, history.First().Commit); + } + } + + [Fact] + public void EmptyRepositoryHasNoHistory() + { + var repoPath = CreateEmptyRepository(); + + using (var repo = new Repository(repoPath)) + { + IEnumerable history = repo.Commits.QueryBy("Test.txt").ToList(); + Assert.Empty(history); + Assert.Empty(history.Blobs()); + } + } + + [Fact] + public void UnsupportedSortStrategyThrows() + { + var repoPath = CreateEmptyRepository(); + + using (var repo = new Repository(repoPath)) + { + // Set up repository. + const string path = "Test.txt"; + MakeAndCommitChange(repo, repoPath, path, "Hello World"); + + Assert.Throws(() => + repo.Commits.QueryBy(path, new CommitFilter + { + SortBy = CommitSortStrategies.None + })); + + Assert.Throws(() => + repo.Commits.QueryBy(path, new CommitFilter + { + SortBy = CommitSortStrategies.Reverse + })); + + Assert.Throws(() => + repo.Commits.QueryBy(path, new CommitFilter + { + SortBy = CommitSortStrategies.Reverse | + CommitSortStrategies.Topological + })); + + Assert.Throws(() => + repo.Commits.QueryBy(path, new CommitFilter + { + SortBy = CommitSortStrategies.Reverse | + CommitSortStrategies.Time + })); + + Assert.Throws(() => + repo.Commits.QueryBy(path, new CommitFilter + { + SortBy = CommitSortStrategies.Reverse | + CommitSortStrategies.Topological | + CommitSortStrategies.Time + })); + } + } + + #region Helpers + + private Signature _signature = Constants.Signature; + private const string SubFolderPath1 = "SubFolder1"; + + private Signature GetNextSignature() + { + _signature = _signature.TimeShift(TimeSpan.FromMinutes(1)); + return _signature; + } + + private string CreateEmptyRepository() + { + // Create a new empty directory with subfolders. + var scd = BuildSelfCleaningDirectory(); + Directory.CreateDirectory(Path.Combine(scd.DirectoryPath, SubFolderPath1)); + + // Initialize a GIT repository in that directory. + Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(scd.DirectoryPath)) + { + repo.Config.Set("user.name", _signature.Name); + repo.Config.Set("user.email", _signature.Email); + } + + // Done. + return scd.DirectoryPath; + } + + /// + /// Adds a commit to the object database. The tree will have a single text file with the given specific content. + /// + /// The repository. + /// The commit message. + /// The file's path. + /// The file's content. + /// The commit's parents. + /// The commit added to the object database. + private Commit AddCommitToOdb(Repository repo, string message, string path, string specificContent, + params Commit[] parents) + { + return AddCommitToOdb(repo, message, path, specificContent, null, parents); + } + + /// + /// Adds a commit to the object database. The tree will have a single text file with the given specific content + /// at the beginning of the file and the given common content at the end of the file. + /// + /// The repository. + /// The commit message. + /// The file's path. + /// The content specific to that file. + /// The content shared with other files. + /// The commit's parents. + /// The commit added to the object database. + private Commit AddCommitToOdb(Repository repo, string message, string path, string specificContent, + string commonContent, params Commit[] parents) + { + var content = string.IsNullOrEmpty(commonContent) + ? specificContent + : specificContent + Environment.NewLine + commonContent + Environment.NewLine; + + var td = new TreeDefinition(); + td.Add(path, OdbHelper.CreateBlob(repo, content), Mode.NonExecutableFile); + var t = repo.ObjectDatabase.CreateTree(td); + + var commitSignature = GetNextSignature(); + + return repo.ObjectDatabase.CreateCommit(commitSignature, commitSignature, message, t, parents, true); + } + + private Commit MakeAndCommitChange(Repository repo, string repoPath, string path, string text, + string message = null) + { + Touch(repoPath, path, text); + Commands.Stage(repo, path); + + var commitSignature = GetNextSignature(); + return repo.Commit(message ?? "Changed " + path, commitSignature, commitSignature); + } + + #endregion + } + + /// + /// Defines extensions used by . + /// + internal static class FileHistoryFixtureExtensions + { + /// + /// Gets the instances contained in each . + /// + /// + /// Use the extension method + /// to retrieve the changed blobs. + /// + /// The file history. + /// The collection of instances included in the file history. + public static IEnumerable Blobs(this IEnumerable fileHistory) + { + return fileHistory.Select(entry => entry.Commit.Tree[entry.Path].Target).OfType(); + } + } +} diff --git a/LibGit2Sharp.Tests/FilterBranchFixture.cs b/LibGit2Sharp.Tests/FilterBranchFixture.cs index bdfb137ee..de4663a22 100644 --- a/LibGit2Sharp.Tests/FilterBranchFixture.cs +++ b/LibGit2Sharp.Tests/FilterBranchFixture.cs @@ -3,7 +3,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -15,7 +14,7 @@ public class FilterBranchFixture : BaseFixture public FilterBranchFixture() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); repo = new Repository(path); } @@ -29,7 +28,7 @@ public override void Dispose() public void CanRewriteHistoryWithoutChangingCommitMetadata() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); // Noop header rewriter repo.Refs.RewriteHistory(new RewriteHistoryOptions @@ -42,14 +41,14 @@ public void CanRewriteHistoryWithoutChangingCommitMetadata() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRewriteHistoryWithoutChangingTrees() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); // Noop tree rewriter repo.Refs.RewriteHistory(new RewriteHistoryOptions @@ -62,14 +61,14 @@ public void CanRewriteHistoryWithoutChangingTrees() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRollbackRewriteByThrowingInOnCompleting() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); Assert.Throws( () => @@ -90,14 +89,14 @@ public void CanRollbackRewriteByThrowingInOnCompleting() AssertSucceedingButNotError(); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitHeaderRewriter() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var thrown = Assert.Throws( () => @@ -117,14 +116,14 @@ public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitHeaderRewrit Assert.Equal("From CommitHeaderRewriter", thrown.InnerException.Message); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitTreeRewriter() { var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var thrown = Assert.Throws( () => @@ -144,13 +143,13 @@ public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitTreeRewriter Assert.Equal("From CommitTreeRewriter", thrown.InnerException.Message); Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); - Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray()); + Assert.Equal(commits, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray()); } [Fact] public void CanRewriteAuthorOfCommits() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); repo.Refs.RewriteHistory(new RewriteHistoryOptions { OnError = OnError, @@ -162,9 +161,8 @@ public void CanRewriteAuthorOfCommits() AssertSucceedingButNotError(); - var nonBackedUpRefs = repo.Refs.Where(x => !x.CanonicalName.StartsWith("refs/original")); - Assert.Empty(repo.Commits.QueryBy(new CommitFilter { Since = nonBackedUpRefs }) - .Where(c => c.Author.Name != "Ben Straub")); + var nonBackedUpRefs = repo.Refs.Where(x => !x.CanonicalName.StartsWith("refs/original/") && !x.CanonicalName.StartsWith("refs/notes/")); + Assert.DoesNotContain(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = nonBackedUpRefs }), c => c.Author.Name != "Ben Straub"); } [Fact] @@ -190,10 +188,10 @@ public void CanRewriteAuthorOfCommitsOnlyBeingPointedAtByTags() AssertSucceedingButNotError(); var lightweightTag = repo.Tags["so-lonely"]; - Assert.Equal("Bam!\n", ((Commit)lightweightTag.Target).Message); + Assert.Equal("Bam!", ((Commit)lightweightTag.Target).Message); var annotatedTag = repo.Tags["so-lonely-but-annotated"]; - Assert.Equal("Bam!\n", ((Commit)annotatedTag.Target).Message); + Assert.Equal("Bam!", ((Commit)annotatedTag.Target).Message); } [Fact] @@ -216,7 +214,7 @@ public void CanRewriteTrees() [Fact] public void CanRewriteTreesByInjectingTreeEntry() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Branches }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches }).ToArray(); var currentReadme = repo.Head["README"]; @@ -233,9 +231,9 @@ public void CanRewriteTreesByInjectingTreeEntry() AssertSucceedingButNotError(); - Assert.Equal(new Commit[0], + Assert.Equal(Array.Empty(), repo.Commits - .QueryBy(new CommitFilter {Since = repo.Branches}) + .QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches }) .Where(c => c["README"] != null && c["README"].Target.Id != currentReadme.Target.Id) .ToArray()); @@ -365,7 +363,7 @@ public void OnlyRewriteSelectedCommits() var commit = repo.Branches["packed"].Tip; var parent = commit.Parents.Single(); - Assert.True(parent.Sha.StartsWith("5001298")); + Assert.StartsWith("5001298", parent.Sha); Assert.NotEqual(Constants.Signature, commit.Author); Assert.NotEqual(Constants.Signature, parent.Author); @@ -402,9 +400,9 @@ public void CanCustomizeTheNamespaceOfBackedUpRefs(string backupRefsNamespace) AssertSucceedingButNotError(); - Assert.NotEmpty(repo.Refs.Where(x => x.CanonicalName.StartsWith("refs/original"))); + Assert.Contains(repo.Refs, x => x.CanonicalName.StartsWith("refs/original")); - Assert.Empty(repo.Refs.Where(x => x.CanonicalName.StartsWith("refs/rewritten"))); + Assert.DoesNotContain(repo.Refs, x => x.CanonicalName.StartsWith("refs/rewritten")); repo.Refs.RewriteHistory(new RewriteHistoryOptions { @@ -417,7 +415,7 @@ public void CanCustomizeTheNamespaceOfBackedUpRefs(string backupRefsNamespace) AssertSucceedingButNotError(); - Assert.NotEmpty(repo.Refs.Where(x => x.CanonicalName.StartsWith("refs/rewritten"))); + Assert.Contains(repo.Refs, x => x.CanonicalName.StartsWith("refs/rewritten")); } [Fact] @@ -493,8 +491,8 @@ public void DoesNotRewriteRefsThatDontChange() // Ensure br2 is still a merge commit var parents = repo.Branches["br2"].Tip.Parents.ToList(); Assert.Equal(2, parents.Count()); - Assert.NotEmpty(parents.Where(c => c.Sha.StartsWith("9fd738e"))); - Assert.Equal("abc\n", parents.Single(c => !c.Sha.StartsWith("9fd738e")).Message); + Assert.Contains(parents, c => c.Sha.StartsWith("9fd738e")); + Assert.Equal("abc", parents.Single(c => !c.Sha.StartsWith("9fd738e")).Message); } [Fact] @@ -529,18 +527,18 @@ public void CanNotOverWriteBackedUpReferences() AssertErrorFired(ex); AssertSucceedingNotFired(); - Assert.Equal("abc\n", repo.Head.Tip.Message); + Assert.Equal("abc", repo.Head.Tip.Message); var newOriginalRefs = repo.Refs.FromGlob("refs/original/*").OrderBy(r => r.CanonicalName).ToArray(); Assert.Equal(originalRefs, newOriginalRefs); - Assert.Empty(repo.Refs.Where(x => x.CanonicalName.StartsWith("refs/original/original/"))); + Assert.DoesNotContain(repo.Refs, x => x.CanonicalName.StartsWith("refs/original/original/")); } [Fact] public void CanNotOverWriteAnExistingReference() { - var commits = repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs }).ToArray(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); var ex = Assert.Throws( () => @@ -556,7 +554,7 @@ public void CanNotOverWriteAnExistingReference() AssertErrorFired(ex); AssertSucceedingNotFired(); - Assert.Equal(0, repo.Refs.FromGlob("refs/original/*").Count()); + Assert.Empty(repo.Refs.FromGlob("refs/original/*")); } // Graft the orphan "test" branch to the tip of "packed" @@ -632,7 +630,7 @@ public void CanRewriteParentWithRewrittenCommit() var commitToRewrite = repo.Lookup("6dcf9bf"); var newParent = repo.Branches["packed"].Tip; - Assert.True(newParent.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", newParent.Sha); repo.Refs.RewriteHistory(new RewriteHistoryOptions { @@ -648,22 +646,22 @@ public void CanRewriteParentWithRewrittenCommit() AssertSucceedingButNotError(); // Assert "packed" hasn't been rewritten - Assert.True(repo.Branches["packed"].Tip.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", repo.Branches["packed"].Tip.Sha); // Assert (test, tag: lw, tag: e90810b, tag: test) have been rewritten var rewrittenTestCommit = repo.Branches["test"].Tip; - Assert.True(rewrittenTestCommit.Sha.StartsWith("f558880")); + Assert.StartsWith("f558880", rewrittenTestCommit.Sha); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/lw^{commit}")); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/e90810b^{commit}")); Assert.Equal(rewrittenTestCommit, repo.Lookup("refs/tags/test^{commit}")); // Assert parent of rewritten commit var rewrittenTestCommitParent = rewrittenTestCommit.Parents.Single(); - Assert.True(rewrittenTestCommitParent.Sha.StartsWith("0c25efa")); + Assert.StartsWith("0c25efa", rewrittenTestCommitParent.Sha); // Assert grand parent of rewritten commit var rewrittenTestCommitGrandParent = rewrittenTestCommitParent.Parents.Single(); - Assert.True(rewrittenTestCommitGrandParent.Sha.StartsWith("41bc8c6")); + Assert.StartsWith("41bc8c6", rewrittenTestCommitGrandParent.Sha); } [Fact] @@ -699,7 +697,7 @@ public void CanProvideNewNamesForTags() CommitHeaderRewriter = c => CommitRewriteInfo.From(c, message: ""), TagNameRewriter = TagNameRewriter, - }, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs["refs/heads/test"] })); + }, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs["refs/heads/test"] })); AssertSucceedingButNotError(); @@ -790,7 +788,7 @@ public void HandlesNameRewritingOfChainedTags() var newCommit = newAnnotationC.Target as Commit; Assert.NotNull(newCommit); Assert.NotEqual(newCommit, theCommit); - Assert.Equal("Rewrote\n", newCommit.Message); + Assert.Equal("Rewrote", newCommit.Message); // Ensure the original tag doesn't exist anymore Assert.Null(repo.Tags["lightweightA"]); @@ -801,6 +799,26 @@ public void HandlesNameRewritingOfChainedTags() Assert.Equal(annotationB, backedUpTag.ResolveToDirectReference().Target); } + [Fact] + public void RewritingNotesHasNoEffect() + { + var notesRefsRetriever = new Func>(() => repo.Refs.Where(r => r.CanonicalName.StartsWith("refs/notes/"))); + var originalNotesRefs = notesRefsRetriever().ToList(); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = originalNotesRefs }).ToArray(); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, author: Constants.Signature), + }, commits); + + AssertSucceedingButNotError(); + + Assert.Equal(originalNotesRefs.OrderBy(r => r.CanonicalName), notesRefsRetriever().OrderBy(r => r.CanonicalName)); + } + private static string TagNameRewriter(string name, bool isAnnotated, string target) { const string tagPrefix = "refs/tags/"; diff --git a/LibGit2Sharp.Tests/FilterFixture.cs b/LibGit2Sharp.Tests/FilterFixture.cs new file mode 100644 index 000000000..8fd9c8392 --- /dev/null +++ b/LibGit2Sharp.Tests/FilterFixture.cs @@ -0,0 +1,486 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class FilterFixture : BaseFixture + { + readonly Action successCallback = (reader, writer) => + { + reader.CopyTo(writer); + }; + + private const string FilterName = "the-filter"; + readonly List attributes = new List { new FilterAttributeEntry("test") }; + + [Fact] + public void CanRegisterFilterWithSingleAttribute() + { + var filter = new EmptyFilter(FilterName, attributes); + Assert.Equal(attributes, filter.Attributes); + } + + [Fact] + public void CanRegisterAndUnregisterTheSameFilter() + { + var filter = new EmptyFilter(FilterName, attributes); + + var registration = GlobalSettings.RegisterFilter(filter); + GlobalSettings.DeregisterFilter(registration); + + var secondRegistration = GlobalSettings.RegisterFilter(filter); + GlobalSettings.DeregisterFilter(secondRegistration); + } + + [Fact] + public void CanRegisterAndDeregisterAfterGarbageCollection() + { + var filterRegistration = GlobalSettings.RegisterFilter(new EmptyFilter(FilterName, attributes)); + + GC.Collect(); + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Fact] + public void SameFilterIsEqual() + { + var filter = new EmptyFilter(FilterName, attributes); + Assert.Equal(filter, filter); + } + + [Fact] + public void InitCallbackNotMadeWhenFilterNeverUsed() + { + bool called = false; + Action initializeCallback = () => + { + called = true; + }; + + var filter = new FakeFilter(FilterName, + attributes, + successCallback, + successCallback, + initializeCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + Assert.False(called); + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void InitCallbackMadeWhenUsingTheFilter() + { + bool called = false; + Action initializeCallback = () => + { + called = true; + }; + + var filter = new FakeFilter(FilterName, + attributes, + successCallback, + successCallback, + initializeCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + Assert.False(called); + + string repoPath = InitNewRepository(); + using (var repo = CreateTestRepository(repoPath)) + { + StageNewFile(repo); + Assert.True(called); + } + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void WhenStagingFileApplyIsCalledWithCleanForCorrectPath() + { + string repoPath = InitNewRepository(); + bool called = false; + + Action clean = (reader, writer) => + { + called = true; + reader.CopyTo(writer); + }; + + var filter = new FakeFilter(FilterName, attributes, clean); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + using (var repo = CreateTestRepository(repoPath)) + { + StageNewFile(repo); + Assert.True(called); + } + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void CleanFilterWritesOutputToObjectTree() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + string repoPath = InitNewRepository(); + + Action cleanCallback = SubstitutionCipherFilter.RotateByThirteenPlaces; + + var filter = new FakeFilter(FilterName, attributes, cleanCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + using (var repo = CreateTestRepository(repoPath)) + { + FileInfo expectedFile = StageNewFile(repo, decodedInput); + var commit = repo.Commit("Clean that file", Constants.Signature, Constants.Signature); + var blob = (Blob)commit.Tree[expectedFile.Name].Target; + + var textDetected = blob.GetContentText(); + Assert.Equal(encodedInput, textDetected); + } + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public async Task CanHandleMultipleSmudgesConcurrently() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + const string branchName = "branch"; + + Action smudgeCallback = SubstitutionCipherFilter.RotateByThirteenPlaces; + + var filter = new FakeFilter(FilterName, attributes, null, smudgeCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + int count = 30; + var tasks = new Task[count]; + + for (int i = 0; i < count; i++) + { + tasks[i] = Task.Run(() => + { + string repoPath = InitNewRepository(); + return CheckoutFileForSmudge(repoPath, branchName, encodedInput); + }); + } + + var files = await Task.WhenAll(tasks); + + foreach (var file in files) + { + string readAllText = File.ReadAllText(file.FullName); + Assert.Equal(decodedInput, readAllText); + } + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void WhenCheckingOutAFileFileSmudgeWritesCorrectFileToWorkingDirectory() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + const string branchName = "branch"; + string repoPath = InitNewRepository(); + + Action smudgeCallback = SubstitutionCipherFilter.RotateByThirteenPlaces; + + var filter = new FakeFilter(FilterName, attributes, null, smudgeCallback); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + FileInfo expectedFile = CheckoutFileForSmudge(repoPath, branchName, encodedInput); + + string combine = Path.Combine(repoPath, "..", expectedFile.Name); + string readAllText = File.ReadAllText(combine); + Assert.Equal(decodedInput, readAllText); + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void CanFilterLargeFiles() + { + const int ContentLength = 128 * 1024 * 1024 - 13; + const char ContentValue = 'x'; + + char[] content = (new string(ContentValue, 1024)).ToCharArray(); + + string repoPath = InitNewRepository(); + + var filter = new FileExportFilter(FilterName, attributes); + var registration = GlobalSettings.RegisterFilter(filter); + + try + { + string filePath = Path.Combine(Directory.GetParent(repoPath).Parent.FullName, Guid.NewGuid().ToString() + ".blob"); + FileInfo contentFile = new FileInfo(filePath); + using (var writer = new StreamWriter(contentFile.OpenWrite()) { AutoFlush = true }) + { + int remain = ContentLength; + + while (remain > 0) + { + int chunkSize = remain > content.Length ? content.Length : remain; + writer.Write(content, 0, chunkSize); + remain -= chunkSize; + } + } + + string attributesPath = Path.Combine(Directory.GetParent(repoPath).Parent.FullName, ".gitattributes"); + FileInfo attributesFile = new FileInfo(attributesPath); + + using (Repository repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + File.WriteAllText(attributesPath, "*.blob filter=test"); + Commands.Stage(repo, attributesFile.Name); + Commands.Stage(repo, contentFile.Name); + repo.Commit("test", Constants.Signature, Constants.Signature); + contentFile.Delete(); + Commands.Checkout(repo, "HEAD", new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + } + + contentFile = new FileInfo(filePath); + Assert.True(contentFile.Exists, "Contents not restored correctly by forced checkout."); + using (StreamReader reader = contentFile.OpenText()) + { + int totalRead = 0; + char[] block = new char[1024]; + int read; + while ((read = reader.Read(block, 0, block.Length)) > 0) + { + Assert.True(CharArrayAreEqual(block, content, read)); + totalRead += read; + } + + Assert.Equal(ContentLength, totalRead); + } + + contentFile.Delete(); + } + finally + { + GlobalSettings.DeregisterFilter(registration); + } + } + + [Fact] + public void DoubleRegistrationFailsButDoubleDeregistrationDoesNot() + { + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + var filter = new EmptyFilter(FilterName, attributes); + var registration = GlobalSettings.RegisterFilter(filter); + + Assert.Throws(() => { GlobalSettings.RegisterFilter(filter); }); + Assert.Single(GlobalSettings.GetRegisteredFilters()); + + Assert.True(registration.IsValid, "FilterRegistration.IsValid should be true."); + + GlobalSettings.DeregisterFilter(registration); + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + Assert.False(registration.IsValid, "FilterRegistration.IsValid should be false."); + + GlobalSettings.DeregisterFilter(registration); + Assert.Empty(GlobalSettings.GetRegisteredFilters()); + + Assert.False(registration.IsValid, "FilterRegistration.IsValid should be false."); + } + + private unsafe bool CharArrayAreEqual(char[] array1, char[] array2, int count) + { + if (ReferenceEquals(array1, array2)) + { + return true; + } + if (ReferenceEquals(array1, null) || ReferenceEquals(null, array2)) + { + return false; + } + if (array1.Length < count || array2.Length < count) + { + return false; + } + + int len = count * sizeof(char); + int cnt = len / sizeof(long); + + fixed (char* c1 = array1, c2 = array2) + { + long* p1 = (long*)c1, + p2 = (long*)c2; + + for (int i = 0; i < cnt; i++) + { + if (p1[i] != p2[i]) + { + return false; + } + } + + byte* b1 = (byte*)c1, + b2 = (byte*)c2; + + for (int i = len * sizeof(long); i < len; i++) + { + if (b1[i] != b2[i]) + { + return false; + } + } + } + + return true; + } + + private FileInfo CheckoutFileForSmudge(string repoPath, string branchName, string content) + { + FileInfo expectedPath; + using (var repo = CreateTestRepository(repoPath)) + { + StageNewFile(repo, content); + + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + + expectedPath = CommitFileOnBranch(repo, branchName, content); + + Commands.Checkout(repo, "master"); + + Commands.Checkout(repo, branchName); + } + return expectedPath; + } + + private static FileInfo CommitFileOnBranch(Repository repo, string branchName, string content) + { + var branch = repo.CreateBranch(branchName); + Commands.Checkout(repo, branch.FriendlyName); + + FileInfo expectedPath = StageNewFile(repo, content); + repo.Commit("Commit", Constants.Signature, Constants.Signature); + return expectedPath; + } + + private static FileInfo StageNewFile(IRepository repo, string contents = "null") + { + string newFilePath = Touch(repo.Info.WorkingDirectory, Guid.NewGuid() + ".txt", contents); + var stageNewFile = new FileInfo(newFilePath); + Commands.Stage(repo, newFilePath); + return stageNewFile; + } + + private Repository CreateTestRepository(string path) + { + var repository = new Repository(path); + CreateConfigurationWithDummyUser(repository, Constants.Identity); + CreateAttributesFile(repository, "* filter=test"); + return repository; + } + + class EmptyFilter : Filter + { + public EmptyFilter(string name, IEnumerable attributes) + : base(name, attributes) + { } + } + + class FakeFilter : Filter + { + private readonly Action cleanCallback; + private readonly Action smudgeCallback; + private readonly Action initCallback; + + public FakeFilter(string name, IEnumerable attributes, + Action cleanCallback = null, + Action smudgeCallback = null, + Action initCallback = null) + : base(name, attributes) + { + this.cleanCallback = cleanCallback; + this.smudgeCallback = smudgeCallback; + this.initCallback = initCallback; + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + if (cleanCallback == null) + { + base.Clean(path, root, input, output); + } + else + { + cleanCallback(input, output); + } + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + if (smudgeCallback == null) + { + base.Smudge(path, root, input, output); + } + else + { + smudgeCallback(input, output); + } + } + + protected override void Initialize() + { + if (initCallback == null) + { + base.Initialize(); + } + else + { + initCallback(); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs b/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs new file mode 100644 index 000000000..89b61c546 --- /dev/null +++ b/LibGit2Sharp.Tests/FilterSubstitutionCipherFixture.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class FilterSubstitutionCipherFixture : BaseFixture + { + [Fact] + public void SmugdeIsNotCalledForFileWhichDoesNotMatchAnAttributeEntry() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + var attributes = new List { new FilterAttributeEntry("rot13") }; + var filter = new SubstitutionCipherFilter("cipher-filter", attributes); + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".rot13"; + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, "*.rot13 filter=rot13"); + + var blob = CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + var textDetected = blob.GetContentText(); + + Assert.Equal(encodedInput, textDetected); + Assert.Equal(1, filter.CleanCalledCount); + Assert.Equal(0, filter.SmudgeCalledCount); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + var fileContents = ReadTextFromFile(repo, fileName); + Assert.Equal(1, filter.SmudgeCalledCount); + Assert.Equal(decodedInput, fileContents); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Fact] + public void CorrectlyEncodesAndDecodesInput() + { + const string decodedInput = "This is a substitution cipher"; + const string encodedInput = "Guvf vf n fhofgvghgvba pvcure"; + + var attributes = new List { new FilterAttributeEntry("rot13") }; + var filter = new SubstitutionCipherFilter("cipher-filter", attributes); + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".rot13"; + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, "*.rot13 filter=rot13"); + + var blob = CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + var textDetected = blob.GetContentText(); + + Assert.Equal(encodedInput, textDetected); + Assert.Equal(1, filter.CleanCalledCount); + Assert.Equal(0, filter.SmudgeCalledCount); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + var fileContents = ReadTextFromFile(repo, fileName); + Assert.Equal(1, filter.SmudgeCalledCount); + Assert.Equal(decodedInput, fileContents); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + [InlineData("*.txt", ".bat", 0, 0)] + [InlineData("*.txt", ".txt", 1, 0)] + public void WhenStagedFileDoesNotMatchPathSpecFileIsNotFiltered(string pathSpec, string fileExtension, int cleanCount, int smudgeCount) + { + const string filterName = "rot13"; + const string decodedInput = "This is a substitution cipher"; + string attributeFileEntry = string.Format("{0} filter={1}", pathSpec, filterName); + + var filterForAttributes = new List { new FilterAttributeEntry(filterName) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + fileExtension; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeFileEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + Assert.Equal(cleanCount, filter.CleanCalledCount); + Assert.Equal(smudgeCount, filter.SmudgeCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + [InlineData("rot13", "*.txt filter=rot13", 1)] + [InlineData("rot13", "*.txt filter=fake", 0)] + [InlineData("rot13", "*.bat filter=rot13", 0)] + [InlineData("fake", "*.txt filter=fake", 1)] + [InlineData("fake", "*.txt filter=rot13", 0)] + [InlineData("fake", "*.bat filter=fake", 0)] + [InlineData("rot13", "*.txt filter=rot13 -crlf", 1)] + public void CleanIsCalledIfAttributeEntryMatches(string filterAttribute, string attributeEntry, int cleanCount) + { + const string decodedInput = "This is a substitution cipher"; + + var filterForAttributes = new List { new FilterAttributeEntry(filterAttribute) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".txt"; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + Assert.Equal(cleanCount, filter.CleanCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + } + + [Theory] + + [InlineData("rot13", "*.txt filter=rot13", 1)] + [InlineData("rot13", "*.txt filter=fake", 0)] + [InlineData("rot13", "*.txt filter=rot13 -crlf", 1)] + public void SmudgeIsCalledIfAttributeEntryMatches(string filterAttribute, string attributeEntry, int smudgeCount) + { + const string decodedInput = "This is a substitution cipher"; + + var filterForAttributes = new List { new FilterAttributeEntry(filterAttribute) }; + var filter = new SubstitutionCipherFilter("cipher-filter", filterForAttributes); + + var filterRegistration = GlobalSettings.RegisterFilter(filter); + + string repoPath = InitNewRepository(); + string fileName = Guid.NewGuid() + ".txt"; + + using (var repo = new Repository(repoPath)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + CreateAttributesFile(repo, attributeEntry); + + CommitOnBranchAndReturnDatabaseBlob(repo, fileName, decodedInput); + + var branch = repo.CreateBranch("delete-files"); + Commands.Checkout(repo, branch.FriendlyName); + + DeleteFile(repo, fileName); + + Commands.Checkout(repo, "master"); + + Assert.Equal(smudgeCount, filter.SmudgeCalledCount); + } + + GlobalSettings.DeregisterFilter(filterRegistration); + + } + + private static string ReadTextFromFile(Repository repo, string fileName) + { + return File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, fileName)); + } + + private static void DeleteFile(Repository repo, string fileName) + { + File.Delete(Path.Combine(repo.Info.WorkingDirectory, fileName)); + Commands.Stage(repo, fileName); + repo.Commit("remove file", Constants.Signature, Constants.Signature); + } + + private static Blob CommitOnBranchAndReturnDatabaseBlob(Repository repo, string fileName, string input) + { + Touch(repo.Info.WorkingDirectory, fileName, input); + Commands.Stage(repo, fileName); + + var commit = repo.Commit("new file", Constants.Signature, Constants.Signature); + + var blob = (Blob)commit.Tree[fileName].Target; + return blob; + } + } +} diff --git a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs index d8533f035..925efc3d0 100644 --- a/LibGit2Sharp.Tests/GlobalSettingsFixture.cs +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -1,4 +1,8 @@ -using System.Text.RegularExpressions; +using System; +using System.IO; +using System.Reflection; +using System.Text.RegularExpressions; +using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -19,34 +23,99 @@ public void CanGetMinimumCompiledInFeatures() public void CanRetrieveValidVersionString() { // Version string format is: - // Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features) + // Major.Minor.Patch[-previewTag]+libgit2-{libgit2_abbrev_hash}.{LibGit2Sharp_hash} (arch - features) // Example output: - // "0.17.0-unknown-06d772d (x86 - Threads, Https)" + // "0.27.0-preview.0.1896+libgit2-c058aa8.c1ac3ed74487da5fac24cf1e48dc8ea71e917b75 (x64 - Threads, Https, NSec)" string versionInfo = GlobalSettings.Version.ToString(); // The GlobalSettings.Version returned string should contain : - // version:'0.17.0' LibGit2Sharp version number. - // git2SharpHash:'unknown' ( when compiled from source ) else LibGit2Sharp library hash. - // git2hash: '06d772d' LibGit2 library hash. - // arch: 'x86' or 'amd64' LibGit2 target. - // git2Features: 'Threads, Ssh' LibGit2 features compiled with. - string regex = @"^(?\d{1,}\.\d{1,2}\.\d{1,3})-(?\w+)-(?\w+) \((?\w+) - (?(?:\w*(?:, )*\w+)*)\)$"; + // version: '0.25.0[-previewTag]' LibGit2Sharp version number. + // git2SharpHash: 'c1ac3ed74487da5fac24cf1e48dc8ea71e917b75' LibGit2Sharp hash. + // arch: 'x86' or 'x64' libgit2 target. + // git2Features: 'Threads, Ssh' libgit2 features compiled with. + string regex = @"^(?\d+\.\d+\.\d+(-[\w\-\.]+)?)\+libgit2-[a-f0-9]{7}\.((?[a-f0-9]{40}))? \((?\w+) - (?(?:\w*(?:, )*\w+)*)\)$"; Assert.NotNull(versionInfo); Match regexResult = Regex.Match(versionInfo, regex); Assert.True(regexResult.Success, "The following version string format is enforced:" + - "Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features)"); + "Major.Minor.Patch[-previewTag]+libgit2-{libgit2_abbrev_hash}.{LibGit2Sharp_hash} (arch - features). " + + "But found \"" + versionInfo + "\" instead."); + } + + [Fact] + public void TryingToResetNativeLibraryPathAfterLoadedThrows() + { + // Do something that loads the native library + var features = GlobalSettings.Version.Features; - GroupCollection matchGroups = regexResult.Groups; + Assert.Throws(() => { GlobalSettings.NativeLibraryPath = "C:/Foo"; }); + } + + [SkippableTheory] + [InlineData("x86")] + [InlineData("x64")] + public void LoadFromSpecifiedPath(string architecture) + { + Skip.IfNot(Platform.IsRunningOnNetFramework(), ".NET Framework only test."); - // Check that all groups are valid - foreach (Group group in matchGroups) + var nativeDllFileName = NativeDllName.Name + ".dll"; + var testDir = Path.GetDirectoryName(typeof(GlobalSettingsFixture).Assembly.Location); + var testAppExe = Path.Combine(testDir, $"NativeLibraryLoadTestApp.{architecture}.exe"); + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var platformDir = Path.Combine(tempDir, "plat", architecture); + var libraryPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "lib", "win32", architecture); + + try + { + Directory.CreateDirectory(platformDir); + File.Copy(Path.Combine(libraryPath, nativeDllFileName), Path.Combine(platformDir, nativeDllFileName)); + + var (output, exitCode) = ProcessHelper.RunProcess(testAppExe, arguments: $@"{NativeDllName.Name} ""{platformDir}""", workingDirectory: tempDir); + + Assert.Empty(output); + Assert.Equal(0, exitCode); + } + finally { - Assert.True(group.Success); + DirectoryHelper.DeleteDirectory(tempDir); } } + + [Fact] + public void SetExtensions() + { + var extensions = GlobalSettings.GetExtensions(); + + // Assert that "noop" is supported by default + Assert.Equal(new[] { "noop", "objectformat", "worktreeconfig" }, extensions); + + // Disable "noop" extensions + GlobalSettings.SetExtensions("!noop"); + extensions = GlobalSettings.GetExtensions(); + Assert.Equal(new[] { "objectformat", "worktreeconfig" }, extensions); + + // Enable two new extensions (it will reset the configuration and "noop" will be enabled) + GlobalSettings.SetExtensions("partialclone", "newext"); + extensions = GlobalSettings.GetExtensions(); + Assert.Equal(new[] { "newext", "noop", "objectformat", "partialclone", "worktreeconfig" }, extensions); + } + + [Fact] + public void OwnerValidation() + { + // Assert that owner validation is enabled by default + Assert.True(GlobalSettings.GetOwnerValidation()); + + // Disable owner validation + GlobalSettings.SetOwnerValidation(false); + Assert.False(GlobalSettings.GetOwnerValidation()); + + // Enable it again + GlobalSettings.SetOwnerValidation(true); + Assert.True(GlobalSettings.GetOwnerValidation()); + } } } diff --git a/LibGit2Sharp.Tests/IgnoreFixture.cs b/LibGit2Sharp.Tests/IgnoreFixture.cs index 0ad15a3f4..fc9d65bc2 100644 --- a/LibGit2Sharp.Tests/IgnoreFixture.cs +++ b/LibGit2Sharp.Tests/IgnoreFixture.cs @@ -11,27 +11,27 @@ public class IgnoreFixture : BaseFixture [Fact] public void TemporaryRulesShouldApplyUntilCleared() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar"); - Assert.True(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.Contains("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); repo.Ignore.AddTemporaryRules(new[] { "*.cs" }); - Assert.False(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.DoesNotContain("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); repo.Ignore.ResetAllTemporaryRules(); - Assert.True(repo.Index.RetrieveStatus().Untracked.Select(s => s.FilePath).Contains("Foo.cs")); + Assert.Contains("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); } } [Fact] public void IsPathIgnoredShouldVerifyWhetherPathIsIgnored() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar"); @@ -51,7 +51,8 @@ public void IsPathIgnoredShouldVerifyWhetherPathIsIgnored() [Fact] public void CallingIsPathIgnoredWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Ignore.IsPathIgnored(string.Empty)); Assert.Throws(() => repo.Ignore.IsPathIgnored(null)); @@ -61,7 +62,8 @@ public void CallingIsPathIgnoredWithBadParamsThrows() [Fact] public void AddingATemporaryRuleWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Ignore.AddTemporaryRules(null)); } @@ -70,15 +72,47 @@ public void AddingATemporaryRuleWithBadParamsThrows() [Fact] public void CanCheckIfAPathIsIgnoredUsingThePreferedPlatformDirectorySeparatorChar() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, ".gitignore", "/NewFolder\n/NewFolder/NewFolder"); Assert.False(repo.Ignore.IsPathIgnored("File.txt")); Assert.True(repo.Ignore.IsPathIgnored("NewFolder")); - Assert.True(repo.Ignore.IsPathIgnored(string.Format(@"NewFolder{0}NewFolder", Path.DirectorySeparatorChar))); - Assert.True(repo.Ignore.IsPathIgnored(string.Format(@"NewFolder{0}NewFolder{0}File.txt", Path.DirectorySeparatorChar))); + Assert.True(repo.Ignore.IsPathIgnored(string.Join("/", "NewFolder", "NewFolder"))); + Assert.True(repo.Ignore.IsPathIgnored(string.Join("/", "NewFolder", "NewFolder", "File.txt"))); + } + } + + [Fact] + public void HonorDeeplyNestedGitIgnoreFile() + { + string path = InitNewRepository(); + using (var repo = new Repository(path)) + { + var gitIgnoreFile = string.Join("/", "deeply", "nested", ".gitignore"); + Touch(repo.Info.WorkingDirectory, gitIgnoreFile, "SmtCounters.h"); + + Commands.Stage(repo, gitIgnoreFile); + repo.Commit("Add .gitignore", Constants.Signature, Constants.Signature); + + Assert.False(repo.RetrieveStatus().IsDirty); + + var ignoredFile = string.Join("/", "deeply", "nested", "SmtCounters.h"); + Touch(repo.Info.WorkingDirectory, ignoredFile, "Content"); + Assert.False(repo.RetrieveStatus().IsDirty); + + var file = string.Join("/", "deeply", "nested", "file.txt"); + Touch(repo.Info.WorkingDirectory, file, "Yeah!"); + + var repositoryStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.True(repositoryStatus.IsDirty); + + Assert.Equal(FileStatus.Ignored, repositoryStatus[ignoredFile].State); + Assert.Equal(FileStatus.NewInWorkdir, repositoryStatus[file].State); + + Assert.True(repo.Ignore.IsPathIgnored(ignoredFile)); + Assert.False(repo.Ignore.IsPathIgnored(file)); } } } diff --git a/LibGit2Sharp.Tests/IndexFixture.cs b/LibGit2Sharp.Tests/IndexFixture.cs index 3f5d5c41b..4c0d150f0 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -10,7 +10,7 @@ namespace LibGit2Sharp.Tests { public class IndexFixture : BaseFixture { - private static readonly string subBranchFile = Path.Combine("1", "branch_file.txt"); + private static readonly string subBranchFile = string.Join("/", "1", "branch_file.txt"); private readonly string[] expectedEntries = new[] { "1.txt", @@ -28,7 +28,8 @@ public class IndexFixture : BaseFixture [Fact] public void CanCountEntriesInIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal(expectedEntries.Count(), repo.Index.Count); } @@ -37,7 +38,8 @@ public void CanCountEntriesInIndex() [Fact] public void CanEnumerateIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal(expectedEntries, repo.Index.Select(e => e.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); @@ -47,7 +49,8 @@ public void CanEnumerateIndex() [Fact] public void CanFetchAnIndexEntryByItsName() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { IndexEntry entry = repo.Index["README"]; Assert.Equal("README", entry.Path); @@ -65,7 +68,8 @@ public void CanFetchAnIndexEntryByItsName() [Fact] public void FetchingAnUnknownIndexEntryReturnsNull() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { IndexEntry entry = repo.Index["I-do-not-exist.txt"]; Assert.Null(entry); @@ -75,7 +79,8 @@ public void FetchingAnUnknownIndexEntryReturnsNull() [Fact] public void ReadIndexWithBadParamsFails() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Throws(() => { IndexEntry entry = repo.Index[null]; }); Assert.Throws(() => { IndexEntry entry = repo.Index[string.Empty]; }); @@ -93,13 +98,13 @@ public void CanRenameAFile() const string oldName = "polite.txt"; - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); Touch(repo.Info.WorkingDirectory, oldName, "hello test file\n"); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(oldName)); - repo.Index.Stage(oldName); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(oldName)); + Commands.Stage(repo, oldName); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(oldName)); // Generated through // $ echo "hello test file" | git hash-object --stdin @@ -111,13 +116,13 @@ public void CanRenameAFile() Signature who = Constants.Signature; repo.Commit("Initial commit", who, who); - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(oldName)); const string newName = "being.frakking.polite.txt"; - repo.Index.Move(oldName, newName); - Assert.Equal(FileStatus.Removed, repo.Index.RetrieveStatus(oldName)); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(newName)); + Commands.Move(repo, oldName, newName); + Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(newName)); Assert.Equal(1, repo.Index.Count); Assert.Equal(expectedHash, repo.Index[newName].Id.Sha); @@ -125,69 +130,76 @@ public void CanRenameAFile() who = who.TimeShift(TimeSpan.FromMinutes(5)); Commit commit = repo.Commit("Fix file name", who, who); - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(oldName)); - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus(newName)); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(newName)); Assert.Equal(expectedHash, commit.Tree[newName].Target.Id.Sha); } } [Theory] - [InlineData("README", FileStatus.Unaltered, "deleted_unstaged_file.txt", FileStatus.Missing, FileStatus.Removed, FileStatus.Staged)] - [InlineData("new_tracked_file.txt", FileStatus.Added, "deleted_unstaged_file.txt", FileStatus.Missing, FileStatus.Nonexistent, FileStatus.Staged)] - [InlineData("modified_staged_file.txt", FileStatus.Staged, "deleted_unstaged_file.txt", FileStatus.Missing, FileStatus.Removed, FileStatus.Staged)] - [InlineData("modified_unstaged_file.txt", FileStatus.Modified, "deleted_unstaged_file.txt", FileStatus.Missing, FileStatus.Removed, FileStatus.Staged)] + [InlineData("README", FileStatus.Unaltered, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.Nonexistent, FileStatus.ModifiedInIndex)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] public void CanMoveAnExistingFileOverANonExistingFile(string sourcePath, FileStatus sourceStatus, string destPath, FileStatus destStatus, FileStatus sourcePostStatus, FileStatus destPostStatus) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(sourceStatus, repo.Index.RetrieveStatus(sourcePath)); - Assert.Equal(destStatus, repo.Index.RetrieveStatus(destPath)); + Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); + Assert.Equal(destStatus, repo.RetrieveStatus(destPath)); - repo.Index.Move(sourcePath, destPath); + Commands.Move(repo, sourcePath, destPath); - Assert.Equal(sourcePostStatus, repo.Index.RetrieveStatus(sourcePath)); - Assert.Equal(destPostStatus, repo.Index.RetrieveStatus(destPath)); + Assert.Equal(sourcePostStatus, repo.RetrieveStatus(sourcePath)); + Assert.Equal(destPostStatus, repo.RetrieveStatus(destPath)); } } [Theory] - [InlineData("README", FileStatus.Unaltered, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - [InlineData("new_tracked_file.txt", FileStatus.Added, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - [InlineData("modified_staged_file.txt", FileStatus.Staged, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - [InlineData("modified_unstaged_file.txt", FileStatus.Modified, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] - public void MovingOverAnExistingFileThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("README", FileStatus.Unaltered)] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir)] + public void MovingOverAnExistingFileThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] + public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] - [InlineData("deleted_unstaged_file.txt", FileStatus.Missing, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - [InlineData("deleted_staged_file.txt", FileStatus.Removed, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - [InlineData("i_dont_exist.txt", FileStatus.Nonexistent, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] - public void MovingAFileNotInTheWorkingDirectoryThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] + [InlineData("i_dont_exist.txt", FileStatus.Nonexistent)] + public void MovingAFileNotInTheWorkingDirectoryThrows(string sourcePath, FileStatus sourceStatus) { + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" }; + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } - private static void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) + private void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { - using (var repo = new Repository(StandardTestRepoPath)) + var repoPath = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(repoPath)) { - Assert.Equal(sourceStatus, repo.Index.RetrieveStatus(sourcePath)); + Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); foreach (var destPath in destPaths) { string path = destPath; - Assert.Throws(() => repo.Index.Move(sourcePath, path)); + Assert.Throws(() => Commands.Move(repo, sourcePath, path)); } } } @@ -196,7 +208,7 @@ private static void InvalidMoveUseCases(string sourcePath, FileStatus sourceStat public void PathsOfIndexEntriesAreExpressedInNativeFormat() { // Build relative path - string relFilePath = Path.Combine("directory", "Testfile.txt"); + string relFilePath = Path.Combine("directory", "Testfile.txt").Replace('\\', '/'); string repoPath = InitNewRepository(); @@ -205,7 +217,7 @@ public void PathsOfIndexEntriesAreExpressedInNativeFormat() Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); // Stage the file - repo.Index.Stage(relFilePath); + Commands.Stage(repo, relFilePath); // Get the index Index index = repo.Index; @@ -224,7 +236,8 @@ public void PathsOfIndexEntriesAreExpressedInNativeFormat() [Fact] public void CanReadIndexEntryAttributes() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Equal(Mode.NonExecutableFile, repo.Index["README"].Mode); Assert.Equal(Mode.ExecutableFile, repo.Index["1/branch_file.txt"].Mode); @@ -241,7 +254,7 @@ public void StagingAFileWhenTheIndexIsLockedThrowsALockedFileException() Touch(repo.Info.Path, "index.lock"); Touch(repo.Info.WorkingDirectory, "newfile", "my my, this is gonna crash\n"); - Assert.Throws(() => repo.Index.Stage("newfile")); + Assert.Throws(() => Commands.Stage(repo, "newfile")); } } @@ -258,22 +271,22 @@ public void CanCopeWithExternalChangesToTheIndex() using (var repoWrite = new Repository(path)) using (var repoRead = new Repository(path)) { - var writeStatus = repoWrite.Index.RetrieveStatus(); + var writeStatus = repoWrite.RetrieveStatus(); Assert.True(writeStatus.IsDirty); Assert.Equal(0, repoWrite.Index.Count); - var readStatus = repoRead.Index.RetrieveStatus(); + var readStatus = repoRead.RetrieveStatus(); Assert.True(readStatus.IsDirty); Assert.Equal(0, repoRead.Index.Count); - repoWrite.Index.Stage("*"); + Commands.Stage(repoWrite, "*"); repoWrite.Commit("message", Constants.Signature, Constants.Signature); - writeStatus = repoWrite.Index.RetrieveStatus(); + writeStatus = repoWrite.RetrieveStatus(); Assert.False(writeStatus.IsDirty); Assert.Equal(2, repoWrite.Index.Count); - readStatus = repoRead.Index.RetrieveStatus(); + readStatus = repoRead.RetrieveStatus(); Assert.False(readStatus.IsDirty); Assert.Equal(2, repoRead.Index.Count); } @@ -282,7 +295,7 @@ public void CanCopeWithExternalChangesToTheIndex() [Fact] public void CanResetFullyMergedIndexFromTree() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); const string testFile = "new_tracked_file.txt"; @@ -293,27 +306,29 @@ public void CanResetFullyMergedIndexFromTree() const string headIndexTreeSha = "e5d221fc5da11a3169bf503d76497c81be3207b6"; Assert.True(repo.Index.IsFullyMerged); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(testFile)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(testFile)); var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); - repo.Index.Reset(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - Assert.True(repo.Index.IsFullyMerged); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(testFile)); + Assert.True(index.IsFullyMerged); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } // Check that the index was persisted to disk. using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(testFile)); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] public void CanResetIndexWithUnmergedEntriesFromTree() { - string path = CloneMergedTestRepo(); + string path = SandboxMergedTestRepo(); const string testFile = "one.txt"; @@ -324,47 +339,168 @@ public void CanResetIndexWithUnmergedEntriesFromTree() const string headIndexTreeSha = "1cb365141a52dfbb24933515820eb3045fbca12b"; Assert.False(repo.Index.IsFullyMerged); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus(testFile)); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(testFile)); var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); - repo.Index.Reset(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - Assert.True(repo.Index.IsFullyMerged); - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus(testFile)); + Assert.True(index.IsFullyMerged); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } // Check that the index was persisted to disk. using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus(testFile)); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] public void CanClearTheIndex() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); const string testFile = "1.txt"; // It is sufficient to check just one of the stage area changes, such as the modified file, // to verify that the index has indeed been read from the tree. using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus(testFile)); - Assert.NotEqual(0, repo.Index.Count); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(testFile)); + var index = repo.Index; + Assert.NotEqual(0, index.Count); + index.Clear(); + Assert.Equal(0, index.Count); + index.Write(); + + Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); + } - repo.Index.Clear(); - Assert.Equal(0, repo.Index.Count); + // Check that the index was persisted to disk. + using (var repo = new Repository(path)) + { + Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); + } + } - Assert.Equal(FileStatus.Removed | FileStatus.Untracked, repo.Index.RetrieveStatus(testFile)); + [Theory] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, FileStatus.NewInWorkdir)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir)] + [InlineData("i_dont_exist.txt", FileStatus.Nonexistent, FileStatus.Nonexistent)] + public void CanRemoveAnEntryFromTheIndex(string pathInTheIndex, FileStatus expectedBeforeStatus, FileStatus expectedAfterStatus) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var before = repo.RetrieveStatus(pathInTheIndex); + Assert.Equal(expectedBeforeStatus, before); + + repo.Index.Remove(pathInTheIndex); + + var after = repo.RetrieveStatus(pathInTheIndex); + Assert.Equal(expectedAfterStatus, after); } + } - // Check that the index was persisted to disk. + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir, FileStatus.NewInIndex)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, FileStatus.ModifiedInIndex)] + public void CanAddAnEntryToTheIndexFromAFileInTheWorkdir(string pathInTheWorkdir, FileStatus expectedBeforeStatus, FileStatus expectedAfterStatus) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var before = repo.RetrieveStatus(pathInTheWorkdir); + Assert.Equal(expectedBeforeStatus, before); + + repo.Index.Add(pathInTheWorkdir); + + var after = repo.RetrieveStatus(pathInTheWorkdir); + Assert.Equal(expectedAfterStatus, after); + } + } + + [Fact] + public void CanAddAnEntryToTheIndexFromABlob() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + const string targetIndexEntryPath = "1.txt"; + var before = repo.RetrieveStatus(targetIndexEntryPath); + Assert.Equal(FileStatus.Unaltered, before); + + var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + + repo.Index.Add(blob, targetIndexEntryPath, Mode.NonExecutableFile); + + var after = repo.RetrieveStatus(targetIndexEntryPath); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, after); + } + } + + [Fact] + public void AddingAnEntryToTheIndexFromAUnknwonFileInTheWorkdirThrows() + { + var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Removed | FileStatus.Untracked, repo.Index.RetrieveStatus(testFile)); + const string filePath = "i_dont_exist.txt"; + var before = repo.RetrieveStatus(filePath); + Assert.Equal(FileStatus.Nonexistent, before); + + Assert.Throws(() => repo.Index.Add(filePath)); } } + + [Fact] + public void CanMimicGitAddAll() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var before = repo.RetrieveStatus(); + Assert.Contains(before, se => se.State == FileStatus.NewInWorkdir); + Assert.Contains(before, se => se.State == FileStatus.ModifiedInWorkdir); + Assert.Contains(before, se => se.State == FileStatus.DeletedFromWorkdir); + + AddSomeCornerCases(repo); + + Commands.Stage(repo, "*"); + + var after = repo.RetrieveStatus(); + Assert.DoesNotContain(after, se => se.State == FileStatus.NewInWorkdir); + Assert.DoesNotContain(after, se => se.State == FileStatus.ModifiedInWorkdir); + Assert.DoesNotContain(after, se => se.State == FileStatus.DeletedFromWorkdir); + } + } + + [Fact] + public void RetrievingAssumedUnchangedMarkedIndexEntries() + { + var path = SandboxAssumeUnchangedTestRepo(); + using (var repo = new Repository(path)) + { + var regularFile = repo.Index["hello.txt"]; + Assert.False(regularFile.AssumeUnchanged); + + var assumeUnchangedFile = repo.Index["world.txt"]; + Assert.True(assumeUnchangedFile.AssumeUnchanged); + } + } + + private static void AddSomeCornerCases(Repository repo) + { + // Turn 1.txt into a directory in the Index + repo.Index.Remove("1.txt"); + var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); + repo.Index.Add(blob, "1.txt/Sneaky", Mode.NonExecutableFile); + + // Turn README into a symlink + Blob linkContent = OdbHelper.CreateBlob(repo, "1.txt/sneaky"); + repo.Index.Add(linkContent, "README", Mode.SymbolicLink); + } } } diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 02d578912..fb81a76a3 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,153 +1,43 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {286E63EB-04DD-4ADE-88D6-041B57800761} - Library - Properties - LibGit2Sharp.Tests - LibGit2Sharp.Tests - v4.0 - 512 - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - true - full - false - bin\Leaks\ - TRACE;DEBUG;NET40;LEAKS - prompt - 4 - + net472;net8.0;net9.0 + - - False - ..\Lib\MoQ\Moq.dll - - - - - - ..\Lib\xUnit\xunit.dll - - - ..\Lib\xUnit\xunit.extensions.dll - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + - - {EE6ED99F-CB12-4683-B055-D28FC7357A34} - LibGit2Sharp - + + + + - - - $(MSBuildProjectDirectory)\..\Lib\NativeBinaries - - - - - - - - + + + + \ No newline at end of file diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.v2.ncrunchproject b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.v2.ncrunchproject deleted file mode 100644 index e24470157..000000000 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.v2.ncrunchproject +++ /dev/null @@ -1,31 +0,0 @@ - - 1000 - false - true - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - - - LibGit2Sharp.Tests.ShadowCopyFixture.CanProbeForNativeBinariesFromAShadowCopiedAssembly - - - Resources\**;Resources\**.* - \ No newline at end of file diff --git a/LibGit2Sharp.Tests/LogFixture.cs b/LibGit2Sharp.Tests/LogFixture.cs new file mode 100644 index 000000000..b8d43fe36 --- /dev/null +++ b/LibGit2Sharp.Tests/LogFixture.cs @@ -0,0 +1,37 @@ +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class LogFixture + { + [Fact] + public void CanEnableAndDisableLogging() + { + // Setting logging produces a log message at level Info, + // ensure that we catch it. + LogLevel level = LogLevel.None; + string message = null; + + GlobalSettings.LogConfiguration = new LogConfiguration(LogLevel.Trace, (l, m) => { level = l; message = m; }); + + Assert.Equal(LogLevel.Info, level); + Assert.Equal("Logging enabled at level Trace", message); + + // Configuring at Warning and higher means that the + // message at level Info should not be produced. + level = LogLevel.None; + message = null; + + GlobalSettings.LogConfiguration = new LogConfiguration(LogLevel.Warning, (l, m) => { level = l; message = m; }); + + Assert.Equal(LogLevel.None, level); + Assert.Null(message); + + // Similarly, turning logging off should produce no messages. + GlobalSettings.LogConfiguration = LogConfiguration.None; + + Assert.Equal(LogLevel.None, level); + Assert.Null(message); + } + } +} diff --git a/LibGit2Sharp.Tests/MergeFixture.cs b/LibGit2Sharp.Tests/MergeFixture.cs index 7801de718..7b1fda718 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; @@ -17,16 +16,17 @@ public void ANewRepoIsFullyMerged() using (var repo = new Repository(repoPath)) { - Assert.Equal(true, repo.Index.IsFullyMerged); + Assert.True(repo.Index.IsFullyMerged); } } [Fact] public void AFullyMergedRepoOnlyContainsStagedIndexEntries() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(true, repo.Index.IsFullyMerged); + Assert.True(repo.Index.IsFullyMerged); foreach (var entry in repo.Index) { @@ -38,9 +38,10 @@ public void AFullyMergedRepoOnlyContainsStagedIndexEntries() [Fact] public void SoftResetARepoWithUnmergedEntriesThrows() { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(false, repo.Index.IsFullyMerged); + Assert.False(repo.Index.IsFullyMerged); var headCommit = repo.Head.Tip; var firstCommitParent = headCommit.Parents.First(); @@ -52,9 +53,10 @@ public void SoftResetARepoWithUnmergedEntriesThrows() [Fact] public void CommitAgainARepoWithUnmergedEntriesThrows() { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(false, repo.Index.IsFullyMerged); + Assert.False(repo.Index.IsFullyMerged); var author = Constants.Signature; Assert.Throws( @@ -65,7 +67,7 @@ public void CommitAgainARepoWithUnmergedEntriesThrows() [Fact] public void CanRetrieveTheBranchBeingMerged() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string firstBranch = "9fd738e8f7967c078dceed8190330fc8648ee56a"; @@ -86,12 +88,12 @@ public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) const string secondBranchFileName = "second branch file.txt"; const string sharedBranchFileName = "first+second branch file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); var originalTreeCount = firstBranch.Tip.Tree.Count; // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). @@ -104,11 +106,11 @@ public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) if (shouldMergeOccurInDetachedHeadState) { // Detaches HEAD - repo.Checkout(secondBranch.Tip); + Commands.Checkout(repo, secondBranch.Tip); } else { - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); } // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). @@ -136,18 +138,18 @@ public void IsUpToDateMerge() { const string sharedBranchFileName = "first+second branch file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); var secondBranch = repo.CreateBranch("SecondBranch"); - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); @@ -163,7 +165,7 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) const string firstBranchFileName = "first branch file.txt"; const string sharedBranchFileName = "first+second branch file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { // Reset the index and the working tree. @@ -173,7 +175,7 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) repo.RemoveUntrackedFiles(); var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -186,11 +188,11 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) if (shouldMergeOccurInDetachedHeadState) { // Detaches HEAD - repo.Checkout(secondBranch.Tip); + Commands.Checkout(repo, secondBranch.Tip); } else { - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); } Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); @@ -202,7 +204,7 @@ public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); Assert.Equal(repo.Head.Tip, mergeResult.Commit); - Assert.Equal(0, repo.Index.RetrieveStatus().Count()); + Assert.Empty(repo.RetrieveStatus()); Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); if (!shouldMergeOccurInDetachedHeadState) @@ -220,11 +222,11 @@ public void ConflictingMergeRepos() const string secondBranchFileName = "second branch file.txt"; const string sharedBranchFileName = "first+second branch file.txt"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -234,7 +236,7 @@ public void ConflictingMergeRepos() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). AddFileCommitToRepo(repo, secondBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "The second branches comment"); // Change file in second branch @@ -244,7 +246,7 @@ public void ConflictingMergeRepos() Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); Assert.Null(mergeResult.Commit); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); var conflict = repo.Index.Conflicts.First(); var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); @@ -260,11 +262,11 @@ public void ConflictingMergeReposBinary() const string secondBranchFileName = "second branch file.bin"; const string sharedBranchFileName = "first+second branch file.bin"; - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var firstBranch = repo.CreateBranch("FirstBranch"); - firstBranch.Checkout(); + Commands.Checkout(repo, firstBranch); // Commit with ONE new file to both first & second branch (SecondBranch is created on this commit). AddFileCommitToRepo(repo, sharedBranchFileName); @@ -274,7 +276,7 @@ public void ConflictingMergeReposBinary() AddFileCommitToRepo(repo, firstBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch - secondBranch.Checkout(); + Commands.Checkout(repo, secondBranch); // Commit with ONE new file to second branch (FirstBranch and SecondBranch now point to separate commits that both have the same parent commit). AddFileCommitToRepo(repo, secondBranchFileName); AddFileCommitToRepo(repo, sharedBranchFileName, "\0The second branches comment\0"); // Change file in second branch @@ -283,7 +285,7 @@ public void ConflictingMergeReposBinary() Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); - Assert.Equal(1, repo.Index.Conflicts.Count()); + Assert.Single(repo.Index.Conflicts); Conflict conflict = repo.Index.Conflicts.First(); @@ -293,6 +295,24 @@ public void ConflictingMergeReposBinary() } } + [Fact] + public void CanFailOnFirstMergeConflict() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge("conflicts", Constants.Signature, new MergeOptions() { FailOnConflict = true, }); + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"]; + var branch = repo.Branches["conflicts"]; + var mergeTreeResult = repo.ObjectDatabase.MergeCommits(master.Tip, branch.Tip, new MergeTreeOptions() { FailOnConflict = true }); + Assert.Equal(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + Assert.Empty(mergeTreeResult.Conflicts); + } + + } + [Theory] [InlineData(true, FastForwardStrategy.Default, fastForwardBranchInitialId, MergeStatus.FastForward)] [InlineData(true, FastForwardStrategy.FastForwardOnly, fastForwardBranchInitialId, MergeStatus.FastForward)] @@ -300,12 +320,12 @@ public void ConflictingMergeReposBinary() [InlineData(false, FastForwardStrategy.FastForwardOnly, fastForwardBranchInitialId, MergeStatus.FastForward)] public void CanFastForwardCommit(bool fromDetachedHead, FastForwardStrategy fastForwardStrategy, string expectedCommitId, MergeStatus expectedMergeStatus) { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { - if(fromDetachedHead) + if (fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -314,24 +334,24 @@ public void CanFastForwardCommit(bool fromDetachedHead, FastForwardStrategy fast Assert.Equal(expectedMergeStatus, result.Status); Assert.Equal(expectedCommitId, result.Commit.Id.Sha); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); Assert.Equal(fromDetachedHead, repo.Info.IsHeadDetached); } } [Theory] [InlineData(true, FastForwardStrategy.Default, MergeStatus.NonFastForward)] - [InlineData(true, FastForwardStrategy.NoFastFoward, MergeStatus.NonFastForward)] + [InlineData(true, FastForwardStrategy.NoFastForward, MergeStatus.NonFastForward)] [InlineData(false, FastForwardStrategy.Default, MergeStatus.NonFastForward)] - [InlineData(false, FastForwardStrategy.NoFastFoward, MergeStatus.NonFastForward)] + [InlineData(false, FastForwardStrategy.NoFastForward, MergeStatus.NonFastForward)] public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrategy fastForwardStrategy, MergeStatus expectedMergeStatus) { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { if (fromDetachedHead) { - repo.Checkout(repo.Head.Tip.Id.Sha); + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); } Commit commitToMerge = repo.Branches["normal_merge"].Tip; @@ -339,7 +359,7 @@ public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrat MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = fastForwardStrategy }); Assert.Equal(expectedMergeStatus, result.Status); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); Assert.Equal(fromDetachedHead, repo.Info.IsHeadDetached); } } @@ -347,7 +367,7 @@ public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrat [Fact] public void MergeReportsCheckoutProgress() { - string repoPath = CloneMergeTestRepo(); + string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { Commit commitToMerge = repo.Branches["normal_merge"].Tip; @@ -359,7 +379,7 @@ public void MergeReportsCheckoutProgress() OnCheckoutProgress = (path, completed, total) => wasCalled = true, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); } @@ -368,7 +388,7 @@ public void MergeReportsCheckoutProgress() [Fact] public void MergeReportsCheckoutNotifications() { - string repoPath = CloneMergeTestRepo(); + string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { Commit commitToMerge = repo.Branches["normal_merge"].Tip; @@ -382,7 +402,7 @@ public void MergeReportsCheckoutNotifications() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); @@ -392,7 +412,7 @@ public void MergeReportsCheckoutNotifications() [Fact] public void FastForwardMergeReportsCheckoutProgress() { - string repoPath = CloneMergeTestRepo(); + string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -404,7 +424,7 @@ public void FastForwardMergeReportsCheckoutProgress() OnCheckoutProgress = (path, completed, total) => wasCalled = true, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); } @@ -413,7 +433,7 @@ public void FastForwardMergeReportsCheckoutProgress() [Fact] public void FastForwardMergeReportsCheckoutNotifications() { - string repoPath = CloneMergeTestRepo(); + string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { Commit commitToMerge = repo.Branches["fast_forward"].Tip; @@ -427,7 +447,7 @@ public void FastForwardMergeReportsCheckoutNotifications() CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, }; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, options); + repo.Merge(commitToMerge, Constants.Signature, options); Assert.True(wasCalled); Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); @@ -446,10 +466,10 @@ public void MergeCanDetectRenames() // but the merge will fail with conflicts if this // change is not detected as a rename. - string repoPath = CloneMergeTestRepo(); + string repoPath = SandboxMergeTestRepo(); using (var repo = new Repository(repoPath)) { - Branch currentBranch = repo.Checkout("rename_source"); + Branch currentBranch = Commands.Checkout(repo, "rename_source"); Assert.NotNull(currentBranch); Branch branchToMerge = repo.Branches["rename"]; @@ -463,7 +483,7 @@ public void MergeCanDetectRenames() [Fact] public void FastForwardNonFastForwardableMergeThrows() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Commit commitToMerge = repo.Branches["normal_merge"].Tip; @@ -474,7 +494,7 @@ public void FastForwardNonFastForwardableMergeThrows() [Fact] public void CanForceFastForwardMergeThroughConfig() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("merge.ff", "only"); @@ -487,44 +507,44 @@ public void CanForceFastForwardMergeThroughConfig() [Fact] public void CanMergeAndNotCommit() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Commit commitToMerge = repo.Branches["normal_merge"].Tip; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { CommitOnSuccess = false}); + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { CommitOnSuccess = false }); Assert.Equal(MergeStatus.NonFastForward, result.Status); - Assert.Equal(null, result.Commit); + Assert.Null(result.Commit); - RepositoryStatus repoStatus = repo.Index.RetrieveStatus(); + RepositoryStatus repoStatus = repo.RetrieveStatus(); // Verify that there is a staged entry. - Assert.Equal(1, repoStatus.Count()); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus("b.txt")); + Assert.Single(repoStatus); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("b.txt")); } } [Fact] public void CanForceNonFastForwardMerge() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Commit commitToMerge = repo.Branches["fast_forward"].Tip; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastFoward }); + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastForward }); Assert.Equal(MergeStatus.NonFastForward, result.Status); Assert.Equal("f58f780d5a0ae392efd4a924450b1bbdc0577d32", result.Commit.Id.Sha); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); } } [Fact] public void CanForceNonFastForwardMergeThroughConfig() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("merge.ff", "false"); @@ -535,23 +555,23 @@ public void CanForceNonFastForwardMergeThroughConfig() Assert.Equal(MergeStatus.NonFastForward, result.Status); Assert.Equal("f58f780d5a0ae392efd4a924450b1bbdc0577d32", result.Commit.Id.Sha); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); } } [Fact] public void VerifyUpToDateMerge() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Commit commitToMerge = repo.Branches["master"].Tip; - MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastFoward }); + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.NoFastForward }); Assert.Equal(MergeStatus.UpToDate, result.Status); - Assert.Equal(null, result.Commit); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.Null(result.Commit); + Assert.False(repo.RetrieveStatus().Any()); } } @@ -562,39 +582,39 @@ public void VerifyUpToDateMerge() [InlineData("fast_forward", FastForwardStrategy.Default, MergeStatus.FastForward)] public void CanMergeCommittish(string committish, FastForwardStrategy strategy, MergeStatus expectedMergeStatus) { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { MergeResult result = repo.Merge(committish, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy }); Assert.Equal(expectedMergeStatus, result.Status); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); } } [Theory] [InlineData(true, FastForwardStrategy.FastForwardOnly)] [InlineData(false, FastForwardStrategy.FastForwardOnly)] - [InlineData(true, FastForwardStrategy.NoFastFoward)] - [InlineData(false, FastForwardStrategy.NoFastFoward)] + [InlineData(true, FastForwardStrategy.NoFastForward)] + [InlineData(false, FastForwardStrategy.NoFastForward)] public void MergeWithWorkDirConflictsThrows(bool shouldStage, FastForwardStrategy strategy) { // Merging the fast_forward branch results in a change to file // b.txt. In this test we modify the file in the working directory // and then attempt to perform a merge. We expect the merge to fail - // due to merge conflicts. + // due to checkout conflicts. string committishToMerge = "fast_forward"; - using (var repo = new Repository(CloneMergeTestRepo())) + using (var repo = new Repository(SandboxMergeTestRepo())) { Touch(repo.Info.WorkingDirectory, "b.txt", "this is an alternate change"); if (shouldStage) { - repo.Index.Stage("b.txt"); + Commands.Stage(repo, "b.txt"); } - Assert.Throws(() => repo.Merge(committishToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy })); + Assert.Throws(() => repo.Merge(committishToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy })); } } @@ -606,7 +626,7 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict const string conflictFile = "a.txt"; const string conflictBranchName = "conflicts"; - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { Branch branch = repo.Branches[conflictBranchName]; @@ -629,7 +649,7 @@ public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflict // Get the blob containing the expected content. Blob expectedBlob = null; - switch(conflictStrategy) + switch (conflictStrategy) { case CheckoutFileConflictStrategy.Theirs: expectedBlob = repo.Lookup(conflict.Theirs.Id); @@ -657,13 +677,12 @@ public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) const string conflictFile = "a.txt"; const string conflictBranchName = "conflicts"; - string path = CloneMergeTestRepo(); - using (var repo = InitIsolatedRepository(path)) + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) { Branch branch = repo.Branches[conflictBranchName]; Assert.NotNull(branch); - var status = repo.Index.RetrieveStatus(); MergeOptions mergeOptions = new MergeOptions() { MergeFileFavor = fileFavorFlag, @@ -675,7 +694,7 @@ public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) // Verify that the index and working directory are clean Assert.True(repo.Index.IsFullyMerged); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Get the blob containing the expected content. Blob expectedBlob = null; @@ -709,40 +728,223 @@ public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) [InlineData("fast_forward", FastForwardStrategy.Default, MergeStatus.FastForward)] public void CanMergeBranch(string branchName, FastForwardStrategy strategy, MergeStatus expectedMergeStatus) { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { - Branch branch = repo. Branches[branchName]; + Branch branch = repo.Branches[branchName]; MergeResult result = repo.Merge(branch, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy }); Assert.Equal(expectedMergeStatus, result.Status); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); } } [Fact] public void CanMergeIntoOrphanedBranch() { - string path = CloneMergeTestRepo(); + string path = SandboxMergeTestRepo(); using (var repo = new Repository(path)) { repo.Refs.Add("HEAD", "refs/heads/orphan", true); // Remove entries from the working directory - foreach(var entry in repo.Index.RetrieveStatus()) + foreach (var entry in repo.RetrieveStatus()) { - repo.Index.Unstage(entry.FilePath); - repo.Index.Remove(entry.FilePath, true); + Commands.Unstage(repo, entry.FilePath); + Commands.Remove(repo, entry.FilePath, true); } // Assert that we have an empty working directory. - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); MergeResult result = repo.Merge("master", Constants.Signature); Assert.Equal(MergeStatus.FastForward, result.Status); Assert.Equal(masterBranchInitialId, result.Commit.Id.Sha); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); + } + } + + + [Fact] + public void CanMergeTreeIntoSameTree() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Branches["master"].Tip; + + var result = repo.ObjectDatabase.MergeCommits(master, master, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CanMergeTreeIntoTreeFromUnbornBranch() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn"); + + Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n"); + repo.Index.Clear(); + Commands.Stage(repo, "README"); + + repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches["unborn"].Tip; + + var result = repo.ObjectDatabase.MergeCommits(master, branch, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.NotNull(result.Tree); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CanMergeCommitsAndDetectConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn"); + + repo.Index.Replace(repo.Lookup("conflicts")); + + repo.Commit("A conflicting world, free of the burden of the history", Constants.Signature, Constants.Signature); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches["unborn"].Tip; + + var result = repo.ObjectDatabase.MergeCommits(master, branch, null); + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + Assert.Null(result.Tree); + Assert.NotEmpty(result.Conflicts); + } + } + + [Fact] + public void CanMergeFastForwardTreeWithoutConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("fast_forward"); + + var result = repo.ObjectDatabase.MergeCommits(master, branch, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + Assert.NotNull(result.Tree); + Assert.Empty(result.Conflicts); + } + } + + [Fact] + public void CanIdentifyConflictsInMergeCommits() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("conflicts"); + + var result = repo.ObjectDatabase.MergeCommits(master, branch, null); + + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + + Assert.Null(result.Tree); + Assert.Single(result.Conflicts); + + var conflict = result.Conflicts.First(); + Assert.Equal(new ObjectId("8e9daea300fbfef6c0da9744c6214f546d55b279"), conflict.Ancestor.Id); + Assert.Equal(new ObjectId("610b16886ca829cebd2767d9196f3c4378fe60b5"), conflict.Ours.Id); + Assert.Equal(new ObjectId("3dd9738af654bbf1c363f6c3bbc323bacdefa179"), conflict.Theirs.Id); + } + } + + [Theory] + [InlineData("conflicts_spaces")] + [InlineData("conflicts_tabs")] + public void CanConflictOnWhitespaceChangeMergeConflict(string branchName) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge(branchName, Constants.Signature, new MergeOptions()); + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"]; + var branch = repo.Branches[branchName]; + var mergeTreeResult = repo.ObjectDatabase.MergeCommits(master.Tip, branch.Tip, new MergeTreeOptions()); + Assert.Equal(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + } + } + + [Theory] + [InlineData("conflicts_spaces")] + [InlineData("conflicts_tabs")] + public void CanIgnoreWhitespaceChangeMergeConflict(string branchName) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var mergeResult = repo.Merge(branchName, Constants.Signature, new MergeOptions() { IgnoreWhitespaceChange = true }); + Assert.NotEqual(MergeStatus.Conflicts, mergeResult.Status); + + var master = repo.Branches["master"]; + var branch = repo.Branches[branchName]; + var mergeTreeResult = repo.ObjectDatabase.MergeCommits(master.Tip, branch.Tip, new MergeTreeOptions() { IgnoreWhitespaceChange = true }); + Assert.NotEqual(MergeTreeStatus.Conflicts, mergeTreeResult.Status); + Assert.Empty(mergeTreeResult.Conflicts); + } + } + + [Fact] + public void CanMergeIntoIndex() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + + using (TransientIndex index = repo.ObjectDatabase.MergeCommitsIntoIndex(master, master, null)) + { + var tree = index.WriteToTree(); + Assert.Equal(master.Tree.Id, tree.Id); + } + } + } + + [Fact] + public void CanMergeIntoIndexWithConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("conflicts"); + + using (TransientIndex index = repo.ObjectDatabase.MergeCommitsIntoIndex(branch, master, null)) + { + Assert.False(index.IsFullyMerged); + + var conflict = index.Conflicts.First(); + + //Resolve the conflict by taking the blob from branch + var blob = repo.Lookup(conflict.Ours.Id); + //Add() does not remove conflict entries for the same path, so they must be explicitly removed first. + index.Remove(conflict.Ours.Path); + index.Add(blob, conflict.Ours.Path, Mode.NonExecutableFile); + + Assert.True(index.IsFullyMerged); + var tree = index.WriteToTree(); + + //Since we took the conflicted blob from the branch, the merged result should be the same as the branch. + Assert.Equal(branch.Tree.Id, tree.Id); + } } } @@ -750,7 +952,7 @@ private Commit AddFileCommitToRepo(IRepository repository, string filename, stri { Touch(repository.Info.WorkingDirectory, filename, content); - repository.Index.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/MetaFixture.cs b/LibGit2Sharp.Tests/MetaFixture.cs index fb1b567cb..1d0a1d101 100644 --- a/LibGit2Sharp.Tests/MetaFixture.cs +++ b/LibGit2Sharp.Tests/MetaFixture.cs @@ -3,10 +3,10 @@ using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -14,9 +14,45 @@ public class MetaFixture { private static readonly HashSet explicitOnlyInterfaces = new HashSet { - typeof(IBelongToARepository), + typeof(IBelongToARepository), typeof(IDiffResult), }; + [Fact] + public void LibGit2SharpPublicInterfacesCoverAllPublicMembers() + { + var methodsMissingFromInterfaces = + from t in typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + where !t.GetTypeInfo().IsInterface + where t.GetTypeInfo().GetInterfaces().Any(i => i.GetTypeInfo().IsPublic && i.Namespace == typeof(IRepository).Namespace && !explicitOnlyInterfaces.Contains(i)) + let interfaceTargetMethods = from i in t.GetTypeInfo().GetInterfaces() + from im in t.GetInterfaceMap(i).TargetMethods + select im + from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) + where !interfaceTargetMethods.Contains(tm) + select t.Name + " has extra method " + tm.Name; + + Assert.Equal("", string.Join(Environment.NewLine, + methodsMissingFromInterfaces.ToArray())); + } + + [Fact] + public void LibGit2SharpExplicitOnlyInterfacesAreIndeedExplicitOnly() + { + var methodsMissingFromInterfaces = + from t in typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + where t.GetInterfaces().Any(explicitOnlyInterfaces.Contains) + let interfaceTargetMethods = from i in t.GetInterfaces() + where explicitOnlyInterfaces.Contains(i) + from im in t.GetTypeInfo().GetInterfaceMap(i).TargetMethods + select im + from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) + where interfaceTargetMethods.Contains(tm) + select t.Name + " has public method " + tm.Name + " which should be explicitly implemented."; + + Assert.Equal("", string.Join(Environment.NewLine, + methodsMissingFromInterfaces.ToArray())); + } + [Fact] public void PublicTestMethodsAreFactsOrTheories() { @@ -25,8 +61,8 @@ public void PublicTestMethodsAreFactsOrTheories() "LibGit2Sharp.Tests.FilterBranchFixture.Dispose", }; - var fixtures = from t in Assembly.GetAssembly(typeof(MetaFixture)).GetExportedTypes() - where t.IsPublic && !t.IsNested + var fixtures = from t in typeof(MetaFixture).GetTypeInfo().Assembly.GetExportedTypes() + where t.GetTypeInfo().IsPublic && !t.IsNested where t.Namespace != typeof(BaseFixture).Namespace // Exclude helpers let methods = t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public) from m in methods @@ -46,12 +82,12 @@ public void TypesInLibGit2DecoratedWithDebuggerDisplayMustFollowTheStandardImplP { var typesWithDebuggerDisplayAndInvalidImplPattern = new List(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - .Where(t => t.GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Any()); + IEnumerable libGit2SharpTypes = typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Length != 0); foreach (Type type in libGit2SharpTypes) { - var debuggerDisplayAttribute = (DebuggerDisplayAttribute)type.GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Single(); + var debuggerDisplayAttribute = (DebuggerDisplayAttribute)type.GetTypeInfo().GetCustomAttributes(typeof(DebuggerDisplayAttribute), false).Single(); if (debuggerDisplayAttribute.Value != "{DebuggerDisplay,nq}") { @@ -74,9 +110,9 @@ public void TypesInLibGit2DecoratedWithDebuggerDisplayMustFollowTheStandardImplP } } - if (typesWithDebuggerDisplayAndInvalidImplPattern.Any()) + if (typesWithDebuggerDisplayAndInvalidImplPattern.Count != 0) { - Assert.True(false, Environment.NewLine + BuildMissingDebuggerDisplayPropertyMessage(typesWithDebuggerDisplayAndInvalidImplPattern)); + Assert.Fail(Environment.NewLine + BuildMissingDebuggerDisplayPropertyMessage(typesWithDebuggerDisplayAndInvalidImplPattern)); } } @@ -86,16 +122,16 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() { var nonTestableTypes = new Dictionary>(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - .Where(t => !t.IsSealed && t.Namespace == typeof(IRepository).Namespace); + IEnumerable libGit2SharpTypes = typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => MustBeMockable(t) && t.Namespace == typeof(IRepository).Namespace); foreach (Type type in libGit2SharpTypes) { - if (type.IsInterface || type.IsEnum || IsStatic(type)) + if (type.GetTypeInfo().IsInterface || type.GetTypeInfo().IsEnum || IsStatic(type)) continue; var nonVirtualMethodNamesForType = GetNonVirtualPublicMethodsNames(type).ToList(); - if (nonVirtualMethodNamesForType.Any()) + if (nonVirtualMethodNamesForType.Count != 0) { nonTestableTypes.Add(type, nonVirtualMethodNamesForType); continue; @@ -105,55 +141,59 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() { nonTestableTypes.Add(type, new List()); } + + if (type.GetTypeInfo().IsAbstract) + { + continue; + } + + try + { + if (type.GetTypeInfo().ContainsGenericParameters) + { + var constructType = type.MakeGenericType(Enumerable.Repeat(typeof(object), type.GetGenericArguments().Length).ToArray()); + Activator.CreateInstance(constructType, true); + } + else + { + Activator.CreateInstance(type, true); + } + } + catch + { + nonTestableTypes.Add(type, new List()); + } } - if (nonTestableTypes.Any()) + if (nonTestableTypes.Count != 0) { - Assert.True(false, Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes)); + Assert.Fail(Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes)); } } - [Fact] - public void LibGit2SharpPublicInterfacesCoverAllPublicMembers() + private static bool MustBeMockable(Type type) { - var methodsMissingFromInterfaces = - from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - where !t.IsInterface - where t.GetInterfaces().Any(i => i.IsPublic && i.Namespace == typeof(IRepository).Namespace && !explicitOnlyInterfaces.Contains(i)) - let interfaceTargetMethods = from i in t.GetInterfaces() - from im in t.GetInterfaceMap(i).TargetMethods - select im - from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) - where !interfaceTargetMethods.Contains(tm) - select t.Name + " has extra method " + tm.Name; - - Assert.Equal("", string.Join(Environment.NewLine, - methodsMissingFromInterfaces.ToArray())); - } + if (type.GetTypeInfo().IsSealed) + { + return false; + } - [Fact] - public void LibGit2SharpExplicitOnlyInterfacesAreIndeedExplicitOnly() - { - var methodsMissingFromInterfaces = - from t in Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - where t.GetInterfaces().Any(explicitOnlyInterfaces.Contains) - let interfaceTargetMethods = from i in t.GetInterfaces() - where explicitOnlyInterfaces.Contains(i) - from im in t.GetInterfaceMap(i).TargetMethods - select im - from tm in t.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance) - where interfaceTargetMethods.Contains(tm) - select t.Name + " has public method " + tm.Name + " which should be explicitly implemented."; + if (type.GetTypeInfo().IsAbstract) + { + return !type.GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().IsSubclassOf(type)) + .All(t => t.GetTypeInfo().IsAbstract || t.GetTypeInfo().IsSealed); + } - Assert.Equal("", string.Join(Environment.NewLine, - methodsMissingFromInterfaces.ToArray())); + return true; } + [Fact] public void EnumsWithFlagsHaveMutuallyExclusiveValues() { - var flagsEnums = Assembly.GetAssembly(typeof(IRepository)).GetExportedTypes() - .Where(t => t.IsEnum && t.GetCustomAttributes(typeof(FlagsAttribute), false).Any()); + var flagsEnums = typeof(IRepository).GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().IsEnum && t.GetTypeInfo().GetCustomAttributes(typeof(FlagsAttribute), false).Length != 0); var overlaps = from t in flagsEnums from int x in Enum.GetValues(t) @@ -217,19 +257,19 @@ private static bool HasEmptyPublicOrProtectedConstructor(Type type) private static bool IsStatic(Type type) { - return type.IsAbstract && type.IsSealed; + return type.GetTypeInfo().IsAbstract && type.GetTypeInfo().IsSealed; } // Related to https://github.com/libgit2/libgit2sharp/issues/644 and https://github.com/libgit2/libgit2sharp/issues/645 [Fact] public void GetEnumeratorMethodsInLibGit2SharpMustBeVirtualForTestability() { - var nonVirtualGetEnumeratorMethods = Assembly.GetAssembly(typeof(IRepository)) + var nonVirtualGetEnumeratorMethods = typeof(IRepository).GetTypeInfo().Assembly .GetExportedTypes() .Where(t => - t.Namespace == typeof (IRepository).Namespace && - !t.IsSealed && - !t.IsAbstract && + t.Namespace == typeof(IRepository).Namespace && + !t.GetTypeInfo().IsSealed && + !t.GetTypeInfo().IsAbstract && t.GetInterfaces().Any(i => i.IsAssignableFrom(typeof(IEnumerable<>)))) .Select(t => t.GetMethod("GetEnumerator")) .Where(m => @@ -237,12 +277,147 @@ public void GetEnumeratorMethodsInLibGit2SharpMustBeVirtualForTestability() (!m.IsVirtual || m.IsFinal)) .ToList(); - foreach (var method in nonVirtualGetEnumeratorMethods) + if (nonVirtualGetEnumeratorMethods.Count != 0) + { + var sb = new StringBuilder(); + + foreach (var method in nonVirtualGetEnumeratorMethods) + { + sb.AppendFormat("GetEnumerator in type '{0}' isn't virtual.{1}", + method.DeclaringType, Environment.NewLine); + } + + Assert.Fail(Environment.NewLine + sb.ToString()); + } + } + + [Fact] + public void NoPublicTypesUnderLibGit2SharpCoreNamespace() + { + const string coreNamespace = "LibGit2Sharp.Core"; + + var types = typeof(IRepository).GetTypeInfo().Assembly + .GetExportedTypes() + .Where(t => t.FullName.StartsWith(coreNamespace + ".")) + + // Ugly hack to circumvent a Mono bug + // cf. https://bugzilla.xamarin.com/show_bug.cgi?id=27010 + .Where(t => !t.FullName.Contains("+")) + .Where(t => t.FullName != "LibGit2Sharp.Core.LeaksContainer") + .ToList(); + + if (types.Count != 0) { - Debug.WriteLine(String.Format("GetEnumerator in type '{0}' isn't virtual.", method.DeclaringType)); + var sb = new StringBuilder(); + + foreach (var type in types) + { + sb.AppendFormat("Public type '{0}' under the '{1}' namespace.{2}", + type.FullName, coreNamespace, Environment.NewLine); + } + + Assert.Fail(Environment.NewLine + sb.ToString()); } + } + + [Fact] + public void NoOptionalParametersinMethods() + { + IEnumerable mis = + from t in typeof(IRepository).GetTypeInfo().Assembly + .GetExportedTypes() + from m in t.GetMethods() + where !m.IsObsolete() + from p in m.GetParameters() + where p.IsOptional + select m.DeclaringType + "." + m.Name; + + var sb = new StringBuilder(); + + foreach (var method in mis.Distinct()) + { + sb.AppendFormat("At least one overload of method '{0}' accepts an optional parameter.{1}", + method, Environment.NewLine); + } + + Assert.Equal("", sb.ToString()); + } - Assert.Empty(nonVirtualGetEnumeratorMethods); + [Fact] + public void NoOptionalParametersinConstructors() + { + IEnumerable mis = + from t in typeof(IRepository).GetTypeInfo().Assembly + .GetExportedTypes() + from c in t.GetConstructors() + from p in c.GetParameters() + where p.IsOptional + select c.DeclaringType.Name; + + var sb = new StringBuilder(); + + foreach (var method in mis.Distinct()) + { + sb.AppendFormat("At least one constructor of type '{0}' accepts an optional parameter.{1}", + method, Environment.NewLine); + } + + Assert.Equal("", sb.ToString()); + } + + [Fact] + public void PublicExtensionMethodsShouldonlyTargetInterfacesOrEnums() + { + IEnumerable mis = + from m in GetInvalidPublicExtensionMethods() + select m.DeclaringType + "." + m.Name; + + var sb = new StringBuilder(); + + foreach (var method in mis.Distinct()) + { + sb.AppendFormat("'{0}' is a public extension method that doesn't target an interface or an enum.{1}", + method, Environment.NewLine); + } + + Assert.Equal("", sb.ToString()); + } + + // Inspired from http://stackoverflow.com/a/299526 + + static IEnumerable GetInvalidPublicExtensionMethods() + { + var query = from type in typeof(IRepository).GetTypeInfo().Assembly.GetTypes() + where type.GetTypeInfo().IsSealed && !type.GetTypeInfo().IsGenericType && !type.IsNested && type.GetTypeInfo().IsPublic + from method in type.GetMethods(BindingFlags.Static | BindingFlags.Public) + where method.IsDefined(typeof(ExtensionAttribute), false) + let parameterType = method.GetParameters()[0].ParameterType + where parameterType != null && !parameterType.GetTypeInfo().IsInterface && !parameterType.GetTypeInfo().IsEnum + select method; + return query; + } + + [Fact] + public void AllIDiffResultsAreInChangesBuilder() + { + var diff = typeof(Diff).GetField("ChangesBuilders", BindingFlags.NonPublic | BindingFlags.Static); + var changesBuilders = (System.Collections.IDictionary)diff.GetValue(null); + + IEnumerable diffResults = typeof(Diff).GetTypeInfo().Assembly.GetExportedTypes() + .Where(type => type.GetTypeInfo().GetInterface("IDiffResult") != null); + + var nonBuilderTypes = diffResults.Where(diffResult => !changesBuilders.Contains(diffResult)); + Assert.False(nonBuilderTypes.Any(), "Classes which implement IDiffResult but are not registered under ChangesBuilders in Diff:" + Environment.NewLine + + string.Join(Environment.NewLine, nonBuilderTypes.Select(type => type.FullName))); + } + } + + internal static class TypeExtensions + { + internal static bool IsObsolete(this MethodInfo methodInfo) + { + var attributes = methodInfo.GetCustomAttributes(false); + return attributes.Any(a => a is ObsoleteAttribute); } } } diff --git a/LibGit2Sharp.Tests/MockingFixture.cs b/LibGit2Sharp.Tests/MockingFixture.cs index 13376fae1..6db7dc645 100644 --- a/LibGit2Sharp.Tests/MockingFixture.cs +++ b/LibGit2Sharp.Tests/MockingFixture.cs @@ -18,7 +18,8 @@ public class MockingFixture : BaseFixture [Fact] public void CanCountCommitsWithConcreteRepository() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commitCounter = new CommitCounter(repo); Assert.Equal(7, commitCounter.NumberOfCommits); @@ -72,8 +73,8 @@ public int NumberOfCommits [Fact] public void CanFakeConfigurationBuildSignature() { - var name = "name"; - var email = "email"; + const string name = "name"; + const string email = "email"; var now = DateTimeOffset.UtcNow; var fakeConfig = new Mock(); diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index bf497afc8..f4ad922f6 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -3,7 +3,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,7 +11,6 @@ public class NetworkFixture : BaseFixture [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository.git")] public void CanListRemoteReferences(string url) { string remoteName = "testRemote"; @@ -22,23 +20,26 @@ public void CanListRemoteReferences(string url) using (var repo = new Repository(repoPath)) { Remote remote = repo.Network.Remotes.Add(remoteName, url); - IList references = repo.Network.ListReferences(remote).ToList(); + IList references = repo.Network.ListReferences(remote).ToList(); - foreach (var directReference in references) + + foreach (var reference in references) { // None of those references point to an existing // object in this brand new repository - Assert.Null(directReference.Target); + Assert.Null(reference.ResolveToDirectReference().Target); } List> actualRefs = references. - Select(directRef => new Tuple(directRef.CanonicalName, directRef.TargetIdentifier)).ToList(); + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); } } } @@ -46,30 +47,31 @@ public void CanListRemoteReferences(string url) [Theory] [InlineData("http://github.com/libgit2/TestGitRepository")] [InlineData("https://github.com/libgit2/TestGitRepository")] - [InlineData("git://github.com/libgit2/TestGitRepository.git")] public void CanListRemoteReferencesFromUrl(string url) { string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { - IList references = repo.Network.ListReferences(url).ToList(); + IList references = repo.Network.ListReferences(url).ToList(); - foreach (var directReference in references) + foreach (var reference in references) { // None of those references point to an existing // object in this brand new repository - Assert.Null(directReference.Target); + Assert.Null(reference.ResolveToDirectReference().Target); } List> actualRefs = references. - Select(directRef => new Tuple(directRef.CanonicalName, directRef.TargetIdentifier)).ToList(); + Select(directRef => new Tuple(directRef.CanonicalName, directRef.ResolveToDirectReference() + .TargetIdentifier)).ToList(); - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); } } } @@ -87,22 +89,26 @@ public void CanListRemoteReferenceObjects() using (var repo = new Repository(clonedRepoPath)) { Remote remote = repo.Network.Remotes[remoteName]; - IEnumerable references = repo.Network.ListReferences(remote); + IEnumerable references = repo.Network.ListReferences(remote).ToList(); - var actualRefs = new List>(); + var actualRefs = new List>(); - foreach(DirectReference reference in references) + foreach (Reference reference in references) { Assert.NotNull(reference.CanonicalName); - Assert.NotNull(reference.Target); - actualRefs.Add(new Tuple(reference.CanonicalName, reference.Target.Id.Sha)); + + var directReference = reference.ResolveToDirectReference(); + + Assert.NotNull(directReference.Target); + actualRefs.Add(new Tuple(reference.CanonicalName, directReference.Target.Id.Sha)); } - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) { - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); } } } @@ -123,16 +129,16 @@ public void CanListRemoteReferencesWithCredentials() var references = repo.Network.ListReferences(remote, Constants.PrivateRepoCredentials); - foreach (var directReference in references) + foreach (var reference in references) { - Assert.NotNull(directReference); + Assert.NotNull(reference); } } } [Theory] [InlineData(FastForwardStrategy.Default)] - [InlineData(FastForwardStrategy.NoFastFoward)] + [InlineData(FastForwardStrategy.NoFastForward)] public void CanPull(FastForwardStrategy fastForwardStrategy) { string url = "https://github.com/libgit2/TestGitRepository"; @@ -144,7 +150,7 @@ public void CanPull(FastForwardStrategy fastForwardStrategy) { repo.Reset(ResetMode.Hard, "HEAD~1"); - Assert.False(repo.Index.RetrieveStatus().Any()); + Assert.False(repo.RetrieveStatus().Any()); Assert.Equal(repo.Lookup("refs/remotes/origin/master~1"), repo.Head.Tip); PullOptions pullOptions = new PullOptions() @@ -155,17 +161,17 @@ public void CanPull(FastForwardStrategy fastForwardStrategy) } }; - MergeResult mergeResult = repo.Network.Pull(Constants.Signature, pullOptions); + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, pullOptions); - if(fastForwardStrategy == FastForwardStrategy.Default || fastForwardStrategy == FastForwardStrategy.FastForwardOnly) + if (fastForwardStrategy == FastForwardStrategy.Default || fastForwardStrategy == FastForwardStrategy.FastForwardOnly) { - Assert.Equal(mergeResult.Status, MergeStatus.FastForward); + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); Assert.Equal(mergeResult.Commit, repo.Branches["refs/remotes/origin/master"].Tip); Assert.Equal(repo.Head.Tip, repo.Branches["refs/remotes/origin/master"].Tip); } else { - Assert.Equal(mergeResult.Status, MergeStatus.NonFastForward); + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); } } } @@ -180,7 +186,7 @@ public void CanPullIntoEmptyRepo() using (var repo = new Repository(repoPath)) { // Set up remote - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up tracking information repo.Branches.Update(repo.Head, @@ -188,9 +194,9 @@ public void CanPullIntoEmptyRepo() b => b.UpstreamBranch = "refs/heads/master"); // Pull! - MergeResult mergeResult = repo.Network.Pull(Constants.Signature, new PullOptions()); + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, new PullOptions()); - Assert.Equal(mergeResult.Status, MergeStatus.FastForward); + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); Assert.Equal(mergeResult.Commit, repo.Branches["refs/remotes/origin/master"].Tip); Assert.Equal(repo.Head.Tip, repo.Branches["refs/remotes/origin/master"].Tip); } @@ -215,9 +221,9 @@ public void PullWithoutMergeBranchThrows() try { - repo.Network.Pull(Constants.Signature, new PullOptions()); + Commands.Pull(repo, Constants.Signature, new PullOptions()); } - catch(MergeFetchHeadNotFoundException ex) + catch (MergeFetchHeadNotFoundException ex) { didPullThrow = true; thrownException = ex; @@ -228,32 +234,65 @@ public void PullWithoutMergeBranchThrows() } } - /* - * git ls-remote http://github.com/libgit2/TestGitRepository - * 49322bb17d3acc9146f98c97d078513228bbf3c0 HEAD - * 0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge - * 49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master - * 42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent - * d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag - * c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{} - * 55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob - * 8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree - * 6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling - */ - /// - /// Expected references on http://github.com/libgit2/TestGitRepository - /// - private static List> ExpectedRemoteRefs = new List>() + [Fact] + public void CanMergeFetchedRefs() { - new Tuple("HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0"), - new Tuple("refs/heads/first-merge", "0966a434eb1a025db6b71485ab63a3bfbea520b6"), - new Tuple("refs/heads/master", "49322bb17d3acc9146f98c97d078513228bbf3c0"), - new Tuple("refs/heads/no-parent", "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"), - new Tuple("refs/tags/annotated_tag", "d96c4e80345534eccee5ac7b07fc7603b56124cb"), - new Tuple("refs/tags/annotated_tag^{}", "c070ad8c08840c8116da865b2d65593a6bb9cd2a"), - new Tuple("refs/tags/blob", "55a1a760df4b86a02094a904dfa511deb5655905"), - new Tuple("refs/tags/commit_tree", "8f50ba15d49353813cc6e20298002c0d17b0a9ee"), - new Tuple("refs/tags/nearly-dangling", "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e"), - }; + string url = "https://github.com/libgit2/TestGitRepository"; + + var scd = BuildSelfCleaningDirectory(); + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + using (var repo = new Repository(clonedRepoPath)) + { + repo.Reset(ResetMode.Hard, "HEAD~1"); + + Assert.False(repo.RetrieveStatus().Any()); + Assert.Equal(repo.Lookup("refs/remotes/origin/master~1"), repo.Head.Tip); + + Commands.Fetch(repo, repo.Head.RemoteName, Array.Empty(), null, null); + + MergeOptions mergeOptions = new MergeOptions() + { + FastForwardStrategy = FastForwardStrategy.NoFastForward + }; + + MergeResult mergeResult = repo.MergeFetchedRefs(Constants.Signature, mergeOptions); + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); + } + } + + [Fact] + public void CanPruneRefs() + { + string url = "https://github.com/libgit2/TestGitRepository"; + + var scd = BuildSelfCleaningDirectory(); + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + var scd2 = BuildSelfCleaningDirectory(); + string clonedRepoPath2 = Repository.Clone(url, scd2.DirectoryPath); + + + using (var repo = new Repository(clonedRepoPath)) + { + repo.Network.Remotes.Add("pruner", clonedRepoPath2); + Commands.Fetch(repo, "pruner", Array.Empty(), null, null); + Assert.NotNull(repo.Refs["refs/remotes/pruner/master"]); + + // Remove the branch from the source repository + using (var repo2 = new Repository(clonedRepoPath2)) + { + repo2.Refs.Remove("refs/heads/master"); + } + + // and by default we don't prune it + Commands.Fetch(repo, "pruner", Array.Empty(), null, null); + Assert.NotNull(repo.Refs["refs/remotes/pruner/master"]); + + // but we do when asked by the user + Commands.Fetch(repo, "pruner", Array.Empty(), new FetchOptions { Prune = true }, null); + Assert.Null(repo.Refs["refs/remotes/pruner/master"]); + } + } } } diff --git a/LibGit2Sharp.Tests/NoteFixture.cs b/LibGit2Sharp.Tests/NoteFixture.cs index ad2e66040..4f23ced5c 100644 --- a/LibGit2Sharp.Tests/NoteFixture.cs +++ b/LibGit2Sharp.Tests/NoteFixture.cs @@ -10,27 +10,29 @@ namespace LibGit2Sharp.Tests public class NoteFixture : BaseFixture { private static readonly Signature signatureNullToken = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.UtcNow); - private static readonly Signature signatureYorah = new Signature("yorah", "yoram.harmelin@gmail.com", Epoch.ToDateTimeOffset(1300557894, 60)); + private static readonly Signature signatureYorah = new Signature("yorah", "yoram.harmelin@gmail.com", DateTimeOffset.FromUnixTimeSeconds(1300557894).ToOffset(TimeSpan.FromMinutes(60))); [Fact] public void RetrievingNotesFromANonExistingGitObjectYieldsNoResult() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var notes = repo.Notes[ObjectId.Zero]; - Assert.Equal(0, notes.Count()); + Assert.Empty(notes); } } [Fact] public void RetrievingNotesFromAGitObjectWhichHasNoNoteYieldsNoResult() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var notes = repo.Notes[new ObjectId("4c062a6361ae6959e06292c1fa5e2822d9c96345")]; - Assert.Equal(0, notes.Count()); + Assert.Empty(notes); } } @@ -54,9 +56,10 @@ public void RetrievingNotesFromAGitObjectWhichHasNoNoteYieldsNoResult() [Fact] public void CanRetrieveNotesFromAGitObject() { - var expectedMessages = new [] { "Just Note, don't you understand?\n", "Nope\n", "Not Nope, Note!\n" }; + var expectedMessages = new[] { "Just Note, don't you understand?\n", "Nope\n", "Not Nope, Note!\n" }; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var notes = repo.Notes[new ObjectId("4a202b346bb0fb0db7eff3cffeb3c70babbd2045")]; @@ -68,7 +71,8 @@ public void CanRetrieveNotesFromAGitObject() [Fact] public void CanRetrieveASpecificNoteFromAKnownNamespace() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var singleNote = repo.Notes["answer", new ObjectId("4a202b346bb0fb0db7eff3cffeb3c70babbd2045")]; Assert.Equal("Nope\n", singleNote.Message); @@ -80,7 +84,8 @@ public void CanGetListOfNotesNamespaces() { var expectedNamespaces = new[] { "answer", "answer2", "commits", }; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(expectedNamespaces, repo.Notes.Namespaces.OrderBy(n => n, StringComparer.Ordinal).ToArray()); @@ -110,7 +115,7 @@ public void CanAccessNotesFromACommit() { var expectedNamespaces = new[] { "Just Note, don't you understand?\n", "Nope\n", "Not Nope, Note!\n" }; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); @@ -127,7 +132,7 @@ public void CanAccessNotesFromACommit() [Fact] public void CanAddANoteOnAGitObject() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); @@ -144,7 +149,7 @@ public void CanAddANoteOnAGitObject() [Fact] public void CreatingANoteWhichAlreadyExistsOverwritesThePreviousNote() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); @@ -163,14 +168,16 @@ public void CreatingANoteWhichAlreadyExistsOverwritesThePreviousNote() [Fact] public void CanAddANoteWithSignatureFromConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - var options = new RepositoryOptions { GlobalConfigurationLocation = configPath }; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); - var note = repo.Notes.Add(commit.Id, "I'm batman!\n", "batmobile"); + + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + var note = repo.Notes.Add(commit.Id, "I'm batman!\n", signature, signature, "batmobile"); var newNote = commit.Notes.Single(); Assert.Equal(note, newNote); @@ -178,14 +185,14 @@ public void CanAddANoteWithSignatureFromConfig() Assert.Equal("I'm batman!\n", newNote.Message); Assert.Equal("batmobile", newNote.Namespace); - AssertCommitSignaturesAre(repo.Lookup("refs/notes/batmobile"), Constants.Signature); + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/batmobile"), Constants.Identity); } } [Fact] public void CanCompareTwoUniqueNotes() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); @@ -220,7 +227,7 @@ public void CanCompareTwoUniqueNotes() [Fact] public void CanRemoveANoteFromAGitObject() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("8496071c1b46c854b31185ea97743be6a8774479"); @@ -248,7 +255,7 @@ public void CanRemoveANoteFromAGitObject() [Fact] public void RemovingANonExistingNoteDoesntThrow() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var commit = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); @@ -260,22 +267,23 @@ public void RemovingANonExistingNoteDoesntThrow() [Fact] public void CanRemoveANoteWithSignatureFromConfig() { - string configPath = CreateConfigurationWithDummyUser(Constants.Signature); - RepositoryOptions options = new RepositoryOptions() { GlobalConfigurationLocation = configPath }; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); - using (var repo = new Repository(path, options)) + using (var repo = new Repository(path)) { + CreateConfigurationWithDummyUser(repo, Constants.Identity); var commit = repo.Lookup("8496071c1b46c854b31185ea97743be6a8774479"); var notes = repo.Notes[commit.Id]; Assert.NotEmpty(notes); - repo.Notes.Remove(commit.Id, repo.Notes.DefaultNamespace); + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + repo.Notes.Remove(commit.Id, signature, signature, repo.Notes.DefaultNamespace); Assert.Empty(notes); - AssertCommitSignaturesAre(repo.Lookup("refs/notes/" + repo.Notes.DefaultNamespace), Constants.Signature); + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/" + repo.Notes.DefaultNamespace), Constants.Identity); } } @@ -288,7 +296,8 @@ public void CanRetrieveTheListOfNotesForAGivenNamespace() new { Blob = "1a550e416326cdb4a8e127a04dd69d7a01b11cf4", Target = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }, }; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(expectedNotes, SortedNotes(repo.Notes["commits"], n => new { Blob = n.BlobId.Sha, Target = n.TargetObjectId.Sha })); @@ -299,6 +308,21 @@ public void CanRetrieveTheListOfNotesForAGivenNamespace() } } + [Fact] + public void CanRetrieveNotesWhenThereAreNotAny() + { + string path = InitNewRepository(); // doesn't reproduce an error when using a sandbox repository so we have to create an actual repo. + using (var repo = new Repository(path)) + { + foreach (var note in repo.Notes) + { + Assert.NotNull(note); + } + Assert.Empty(repo.Notes); + } + } + + private static T[] SortedNotes(IEnumerable notes, Func selector) { return notes.OrderBy(n => n.Message, StringComparer.Ordinal).Select(selector).ToArray(); diff --git a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs index 622891aa7..34d3eb77c 100644 --- a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs +++ b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs @@ -17,7 +17,8 @@ public class ObjectDatabaseFixture : BaseFixture [InlineData("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef", false)] public void CanTellIfObjectsExists(string sha, bool shouldExists) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var oid = new ObjectId(sha); @@ -31,7 +32,7 @@ public void CanCreateABlobFromAFileInTheWorkingDirectory() string path = InitNewRepository(); using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("hello.txt")); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus("hello.txt")); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "hello.txt"), "I'm a new file\n"); @@ -40,7 +41,7 @@ public void CanCreateABlobFromAFileInTheWorkingDirectory() Assert.Equal("dc53d4c6b8684c21b0b57db29da4a2afea011565", blob.Sha); /* The file is unknown from the Index nor the Head ... */ - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus("hello.txt")); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("hello.txt")); /* ...however, it's indeed stored in the repository. */ var fetchedBlob = repo.Lookup(blob.Id); @@ -48,6 +49,25 @@ public void CanCreateABlobFromAFileInTheWorkingDirectory() } } + [Fact] + public void RetrieveObjectMetadataReturnsCorrectSizeAndTypeForBlob() + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + Blob blob = CreateBlob(repo, "I'm a new file\n"); + Assert.NotNull(blob); + + GitObjectMetadata blobMetadata = repo.ObjectDatabase.RetrieveObjectMetadata(blob.Id); + Assert.Equal(blobMetadata.Size, blob.Size); + Assert.Equal(ObjectType.Blob, blobMetadata.Type); + + Blob fetchedBlob = repo.Lookup(blob.Id); + Assert.Equal(blobMetadata.Size, fetchedBlob.Size); + } + } + [Fact] public void CanCreateABlobIntoTheDatabaseOfABareRepository() { @@ -83,15 +103,13 @@ public void CanCreateABlobIntoTheDatabaseOfABareRepository() [InlineData("e9671e138a780833cb689753570fd10a55be84fb", "dummy.guess")] public void CanCreateABlobFromAStream(string expectedSha, string hintPath) { - string path = InitNewRepository(); - var sb = new StringBuilder(); for (int i = 0; i < 6; i++) { sb.Append("libgit2\n\r\n"); } - using (var repo = new Repository(path)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFiles(Path.Combine(repo.Info.Path, "info"), "attributes"); @@ -103,6 +121,31 @@ public void CanCreateABlobFromAStream(string expectedSha, string hintPath) } } + [Fact] + public void CanWriteABlobFromAByteArray() + { + var ba = Encoding.ASCII.GetBytes("libgit2\r\n"); + + using (var repo = new Repository(InitNewRepository())) + { + var id = repo.ObjectDatabase.Write(ba); + Assert.Equal(new ObjectId("99115ea359379a218c47cffc83cd0af8c91c4061"), id); + } + } + + [Fact] + public void CanWriteABlobFromAStream() + { + var ba = Encoding.ASCII.GetBytes("libgit2\r\n"); + + using (var stream = new MemoryStream(ba)) + using (var repo = new Repository(InitNewRepository())) + { + var id = repo.ObjectDatabase.Write(stream, stream.Length); + Assert.Equal(new ObjectId("99115ea359379a218c47cffc83cd0af8c91c4061"), id); + } + } + Stream PrepareMemoryStream(int contentSize) { var sb = new StringBuilder(); @@ -183,7 +226,7 @@ private static void CreateAttributesFiles(string where, string filename) [InlineData("1")] public void CanCreateATreeByAlteringAnExistingOne(string targetPath) { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var blob = repo.Lookup(new ObjectId("a8233120f6ad708f843d861ce2b7228ec4e3dec6")); @@ -199,7 +242,7 @@ public void CanCreateATreeByAlteringAnExistingOne(string targetPath) [Fact] public void CanCreateATreeByRemovingEntriesFromExistingOne() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree) @@ -220,7 +263,7 @@ public void CanCreateATreeByRemovingEntriesFromExistingOne() [Fact] public void RemovingANonExistingEntryFromATreeDefinitionHasNoSideEffect() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tree head = repo.Head.Tip.Tree; @@ -256,7 +299,7 @@ public void CanCreateAnEmptyTree() [Fact] public void CanReplaceAnExistingTreeWithAnotherPersitedTree() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -277,10 +320,10 @@ public void CanReplaceAnExistingTreeWithAnotherPersitedTree() [Fact] public void CanCreateATreeContainingABlobFromAFileInTheWorkingDirectory() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("hello.txt")); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus("hello.txt")); File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, "hello.txt"), "I'm a new file\n"); TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree) @@ -309,7 +352,7 @@ public void CanCreateATreeContainingABlobFromAFileInTheWorkingDirectory() [Fact] public void CanCreateATreeContainingAGitLinkFromAnUntrackedSubmoduleInTheWorkingDirectory() { - string path = CloneSubmoduleTestRepo(); + string path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { const string submodulePath = "sm_added_and_uncommited"; @@ -351,7 +394,8 @@ public void CanCreateATreeContainingAGitLinkFromAnUntrackedSubmoduleInTheWorking [Fact] public void CannotCreateATreeContainingABlobFromARelativePathAgainstABareRepository() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var td = new TreeDefinition() .Add("1/new file", "hello.txt", Mode.NonExecutableFile); @@ -363,7 +407,8 @@ public void CannotCreateATreeContainingABlobFromARelativePathAgainstABareReposit [Fact] public void CreatingATreeFromIndexWithUnmergedEntriesThrows() { - using (var repo = new Repository(MergedTestRepoWorkingDirPath)) + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) { Assert.False(repo.Index.IsFullyMerged); @@ -375,7 +420,7 @@ public void CreatingATreeFromIndexWithUnmergedEntriesThrows() [Fact] public void CanCreateATreeFromIndex() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { @@ -397,7 +442,7 @@ public void CanCreateATreeFromIndex() [Fact] public void CanCreateACommit() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Branch head = repo.Head; @@ -429,7 +474,7 @@ public void CanCreateABinaryBlobFromAStream() { Blob blob = repo.ObjectDatabase.CreateBlob(stream); Assert.Equal(6, blob.Size); - Assert.Equal(true, blob.IsBinary); + Assert.True(blob.IsBinary); } } } @@ -437,7 +482,7 @@ public void CanCreateABinaryBlobFromAStream() [Fact] public void CanCreateATagAnnotationPointingToAGitObject() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var blob = repo.Head.Tip["README"].Target as Blob; @@ -463,7 +508,8 @@ public void CanCreateATagAnnotationPointingToAGitObject() [Fact] public void CanEnumerateTheGitObjectsFromBareRepository() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { int count = 0; @@ -485,7 +531,8 @@ public void CanEnumerateTheGitObjectsFromBareRepository() [InlineData("\0\0\0")] public void CreatingACommitWithMessageContainingZeroByteThrows(string message) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.ObjectDatabase.CreateCommit( Constants.Signature, Constants.Signature, message, repo.Head.Tip.Tree, Enumerable.Empty(), false)); @@ -500,7 +547,8 @@ public void CreatingACommitWithMessageContainingZeroByteThrows(string message) [InlineData("\0\0\0")] public void CreatingATagAnnotationWithNameOrMessageContainingZeroByteThrows(string input) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( input, repo.Head.Tip, Constants.Signature, "message")); @@ -512,7 +560,8 @@ public void CreatingATagAnnotationWithNameOrMessageContainingZeroByteThrows(stri [Fact] public void CreatingATagAnnotationWithBadParametersThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( null, repo.Head.Tip, Constants.Signature, "message")); @@ -530,7 +579,7 @@ public void CreatingATagAnnotationWithBadParametersThrows() [Fact] public void CanCreateATagAnnotationWithAnEmptyMessage() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var tagAnnotation = repo.ObjectDatabase.CreateTagAnnotation( @@ -547,7 +596,8 @@ public void CanCalculateHistoryDivergence( string sinceSha, string untilSha, string expectedAncestorSha, int? expectedAheadBy, int? expectedBehindBy) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var since = repo.Lookup(sinceSha); var until = repo.Lookup(untilSha); @@ -566,7 +616,8 @@ public void CanCalculateHistoryDivergenceWhenNoAncestorIsShared( string sinceSha, string untilSha, int? expectedAheadBy, int? expectedBehindBy) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var since = repo.Lookup(sinceSha); var until = repo.Lookup(untilSha); @@ -582,7 +633,8 @@ public void CanCalculateHistoryDivergenceWhenNoAncestorIsShared( [Fact] public void CalculatingHistoryDivergenceWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws( () => repo.ObjectDatabase.CalculateHistoryDivergence(repo.Head.Tip, null)); @@ -602,7 +654,7 @@ public void CanShortenObjectIdentifier() * dea509d097ce692e167dfc6a48a7a280cc5e877e */ - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Config.Set("core.abbrev", 4); @@ -625,6 +677,90 @@ public void CanShortenObjectIdentifier() } } + [Fact] + public void TestMergeIntoSelfHasNoConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + + var result = repo.ObjectDatabase.CanMergeWithoutConflict(master, master); + + Assert.True(result); + } + } + + [Fact] + public void TestMergeIntoOtherUnbornBranchHasNoConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn"); + + Touch(repo.Info.WorkingDirectory, "README", "Yeah!\n"); + repo.Index.Clear(); + Commands.Stage(repo, "README"); + + repo.Commit("A new world, free of the burden of the history", Constants.Signature, Constants.Signature); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches["unborn"].Tip; + + Assert.True(repo.ObjectDatabase.CanMergeWithoutConflict(master, branch)); + } + } + + [Fact] + public void TestMergeIntoOtherUnbornBranchHasConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/unborn"); + + repo.Index.Replace(repo.Lookup("conflicts")); + + repo.Commit("A conflicting world, free of the burden of the history", Constants.Signature, Constants.Signature); + + var master = repo.Branches["master"].Tip; + var branch = repo.Branches["unborn"].Tip; + + Assert.False(repo.ObjectDatabase.CanMergeWithoutConflict(master, branch)); + } + } + + [Fact] + public void TestMergeIntoOtherBranchHasNoConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("fast_forward"); + + var result = repo.ObjectDatabase.CanMergeWithoutConflict(master, branch); + + Assert.True(result); + } + } + + [Fact] + public void TestMergeIntoWrongBranchHasConflicts() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Lookup("master"); + var branch = repo.Lookup("conflicts"); + + var result = repo.ObjectDatabase.CanMergeWithoutConflict(master, branch); + + Assert.False(result); + } + } + private static Blob CreateBlob(Repository repo, string content) { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) diff --git a/LibGit2Sharp.Tests/ObjectIdFixture.cs b/LibGit2Sharp.Tests/ObjectIdFixture.cs index e118cfdb8..8d3468bdd 100644 --- a/LibGit2Sharp.Tests/ObjectIdFixture.cs +++ b/LibGit2Sharp.Tests/ObjectIdFixture.cs @@ -133,7 +133,7 @@ public void TryParse(string maybeSha, bool isValidSha) Assert.NotNull(parsedObjectId); Assert.Equal(maybeSha, parsedObjectId.Sha); - Assert.True(maybeSha.StartsWith(parsedObjectId.ToString(3))); + Assert.StartsWith(parsedObjectId.ToString(3), maybeSha); Assert.Equal(maybeSha, parsedObjectId.ToString(42)); } diff --git a/LibGit2Sharp.Tests/OdbBackendFixture.cs b/LibGit2Sharp.Tests/OdbBackendFixture.cs index c99442c9b..65011ce0f 100644 --- a/LibGit2Sharp.Tests/OdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/OdbBackendFixture.cs @@ -16,7 +16,7 @@ private static Commit AddCommitToRepo(IRepository repo) { string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, content); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var ie = repo.Index[relativeFilepath]; Assert.NotNull(ie); @@ -26,9 +26,9 @@ private static Commit AddCommitToRepo(IRepository repo) var commit = repo.Commit("Initial commit", author, author); relativeFilepath = "big.txt"; - var zeros = new string('0', 32*1024 + 3); + var zeros = new string('0', 32 * 1024 + 3); Touch(repo.Info.WorkingDirectory, relativeFilepath, zeros); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); ie = repo.Index[relativeFilepath]; Assert.NotNull(ie); @@ -90,37 +90,46 @@ public void CanGeneratePredictableObjectShasWithAProvidedBackend() [Fact] public void CanRetrieveObjectsThroughOddSizedShortShas() { - string repoPath = InitNewRepository(); - - using (var repo = new Repository(repoPath)) + try { - var backend = new MockOdbBackend(); - repo.ObjectDatabase.AddBackend(backend, priority: 5); + GlobalSettings.SetStrictHashVerification(false); - AddCommitToRepo(repo); + string repoPath = InitNewRepository(); - var blob1 = repo.Lookup("9daeaf"); - Assert.NotNull(blob1); + using (var repo = new Repository(repoPath)) + { + var backend = new MockOdbBackend(); + repo.ObjectDatabase.AddBackend(backend, priority: 5); - const string dummy = "dummy\n"; + AddCommitToRepo(repo); - // Inserts a fake blob with a similarly prefixed sha - var fakeId = new ObjectId("9daeaf0000000000000000000000000000000000"); - using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(dummy))) - { - Assert.Equal(0, backend.Write(fakeId, ms, dummy.Length, ObjectType.Blob)); - } + var blob1 = repo.Lookup("9daeaf"); + Assert.NotNull(blob1); - var blob2 = repo.Lookup(fakeId); - Assert.NotNull(blob2); + const string dummy = "dummy\n"; - Assert.Throws(() => repo.Lookup("9daeaf")); + // Inserts a fake blob with a similarly prefixed sha + var fakeId = new ObjectId("9daeaf0000000000000000000000000000000000"); + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(dummy))) + { + Assert.Equal(0, backend.Write(fakeId, ms, dummy.Length, ObjectType.Blob)); + } + + var blob2 = repo.Lookup(fakeId); + Assert.NotNull(blob2); - var newBlob1 = repo.Lookup("9daeafb"); - var newBlob2 = repo.Lookup("9daeaf0"); + Assert.Throws(() => repo.Lookup("9daeaf")); - Assert.Equal(blob1, newBlob1); - Assert.Equal(blob2, newBlob2); + var newBlob1 = repo.Lookup("9daeafb"); + var newBlob2 = repo.Lookup("9daeaf0"); + + Assert.Equal(blob1, newBlob1); + Assert.Equal(blob2, newBlob2); + } + } + finally + { + GlobalSettings.SetStrictHashVerification(true); } } @@ -136,7 +145,7 @@ public void CanEnumerateTheContentOfTheObjectDatabase() AddCommitToRepo(repo); - var expected = new[]{ "1fe3126", "2b297e6", "6518215", "9daeafb" }; + var expected = new[] { "1fe3126", "2b297e6", "6518215", "9daeafb" }; IEnumerable objs = repo.ObjectDatabase; @@ -188,7 +197,7 @@ public void CanShortenObjectIdentifier() * dea509d097ce692e167dfc6a48a7a280cc5e877e */ - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.ObjectDatabase.AddBackend(new MockOdbBackend(), 5); @@ -287,7 +296,7 @@ public override int Read(ObjectId oid, out UnmanagedMemoryStream data, out Objec if (!m_objectIdToContent.TryGetValue(oid, out gitObject)) { - return (int) ReturnCode.GIT_ENOTFOUND; + return (int)ReturnCode.GIT_ENOTFOUND; } data = Allocate(gitObject.Length); @@ -402,7 +411,7 @@ public override int ExistsPrefix(string shortSha, out ObjectId found) if (numFound > 1) { found = null; - return (int) ReturnCode.GIT_EAMBIGUOUS; + return (int)ReturnCode.GIT_EAMBIGUOUS; } } diff --git a/LibGit2Sharp.Tests/PackBuilderFixture.cs b/LibGit2Sharp.Tests/PackBuilderFixture.cs new file mode 100644 index 000000000..3d0071df0 --- /dev/null +++ b/LibGit2Sharp.Tests/PackBuilderFixture.cs @@ -0,0 +1,192 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class PackBuilderFixture : BaseFixture + { + [Fact] + public void TestDefaultPackDelegate() + { + TestIfSameRepoAfterPacking(null); + } + + [Fact] + public void TestCommitsPerBranchPackDelegate() + { + TestIfSameRepoAfterPacking(AddingObjectIdsTestDelegate); + } + + [Fact] + public void TestCommitsPerBranchIdsPackDelegate() + { + TestIfSameRepoAfterPacking(AddingObjectsTestDelegate); + } + + internal void TestIfSameRepoAfterPacking(Action packDelegate) + { + // read a repo + // pack with the provided action + // write the pack file in a mirror repo + // read new repo + // compare + + string orgRepoPath = SandboxPackBuilderTestRepo(); + string mrrRepoPath = SandboxPackBuilderTestRepo(); + string mrrRepoPackDirPath = Path.Combine(mrrRepoPath + "/.git/objects"); + + DirectoryHelper.DeleteDirectory(mrrRepoPackDirPath); + Directory.CreateDirectory(mrrRepoPackDirPath + "/pack"); + + PackBuilderOptions packBuilderOptions = new PackBuilderOptions(mrrRepoPackDirPath + "/pack"); + + using (Repository orgRepo = new Repository(orgRepoPath)) + { + PackBuilderResults results; + if (packDelegate != null) + results = orgRepo.ObjectDatabase.Pack(packBuilderOptions, b => packDelegate(orgRepo, b)); + else + results = orgRepo.ObjectDatabase.Pack(packBuilderOptions); + + // written objects count is the same as in objects database + Assert.Equal(orgRepo.ObjectDatabase.Count(), results.WrittenObjectsCount); + + // loading a repo from the written pack file. + using (Repository mrrRepo = new Repository(mrrRepoPath)) + { + // make sure the objects of the original repo are the same as the ones in the mirror repo + // doing that by making sure the count is the same, and the set difference is empty + Assert.True(mrrRepo.ObjectDatabase.Count() == orgRepo.ObjectDatabase.Count() && !mrrRepo.ObjectDatabase.Except(orgRepo.ObjectDatabase).Any()); + + Assert.Equal(orgRepo.Commits.Count(), mrrRepo.Commits.Count()); + Assert.Equal(orgRepo.Branches.Count(), mrrRepo.Branches.Count()); + Assert.Equal(orgRepo.Refs.Count(), mrrRepo.Refs.Count()); + } + } + } + + internal void AddingObjectIdsTestDelegate(IRepository repo, PackBuilder builder) + { + foreach (Branch branch in repo.Branches) + { + foreach (Commit commit in branch.Commits) + { + builder.AddRecursively(commit.Id); + } + } + + foreach (Tag tag in repo.Tags) + { + builder.Add(tag.Target.Id); + } + } + + internal void AddingObjectsTestDelegate(IRepository repo, PackBuilder builder) + { + foreach (Branch branch in repo.Branches) + { + foreach (Commit commit in branch.Commits) + { + builder.AddRecursively(commit); + } + } + + foreach (Tag tag in repo.Tags) + { + builder.Add(tag.Target); + } + } + + [Fact] + public void ExceptionIfPathDoesNotExist() + { + Assert.Throws(() => new PackBuilderOptions("aaa")); + } + + [Fact] + public void ExceptionIfPathEqualsNull() + { + Assert.Throws(() => new PackBuilderOptions(null)); + } + + [Fact] + public void ExceptionIfOptionsEqualsNull() + { + string orgRepoPath = SandboxPackBuilderTestRepo(); + + using (Repository orgRepo = new Repository(orgRepoPath)) + { + Assert.Throws(() => + { + orgRepo.ObjectDatabase.Pack(null); + }); + } + } + + [Fact] + public void ExceptionIfBuildDelegateEqualsNull() + { + string orgRepoPath = SandboxPackBuilderTestRepo(); + PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + + using (Repository orgRepo = new Repository(orgRepoPath)) + { + Assert.Throws(() => + { + orgRepo.ObjectDatabase.Pack(packBuilderOptions, null); + }); + } + } + + [Fact] + public void ExceptionIfNegativeNumberOfThreads() + { + string orgRepoPath = SandboxPackBuilderTestRepo(); + PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + + Assert.Throws(() => + { + packBuilderOptions.MaximumNumberOfThreads = -1; + }); + } + + [Fact] + public void ExceptionIfAddNullObjectID() + { + string orgRepoPath = SandboxPackBuilderTestRepo(); + PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + + using (Repository orgRepo = new Repository(orgRepoPath)) + { + Assert.Throws(() => + { + orgRepo.ObjectDatabase.Pack(packBuilderOptions, builder => + { + builder.Add(null); + }); + }); + } + } + + [Fact] + public void ExceptionIfAddRecursivelyNullObjectID() + { + string orgRepoPath = SandboxPackBuilderTestRepo(); + PackBuilderOptions packBuilderOptions = new PackBuilderOptions(orgRepoPath); + + using (Repository orgRepo = new Repository(orgRepoPath)) + { + Assert.Throws(() => + { + orgRepo.ObjectDatabase.Pack(packBuilderOptions, builder => + { + builder.AddRecursively(null); + }); + }); + } + } + } +} diff --git a/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs b/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs new file mode 100644 index 000000000..ff4949aa4 --- /dev/null +++ b/LibGit2Sharp.Tests/PatchEntryChangesFixture.cs @@ -0,0 +1,44 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class PatchEntryChangesFixture : BaseFixture + { + [Fact] + public void PatchEntryBasics() + { + // Init test repo + var path = SandboxStandardTestRepoGitDir(); + string file = "numbers.txt"; + + // The repo + using (var repo = new Repository(path)) + { + Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; + Tree commitTreeWithUpdatedFile = repo.Lookup("ec9e401").Tree; + + // Create patch by diffing + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + PatchEntryChanges entryChanges = patch[file]; + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(187, entryChanges.Patch.Length); + // Smoke test + Assert.Equal(Mode.NonExecutableFile, entryChanges.Mode); + Assert.Equal(new ObjectId("4625a3628cb78970c57e23a2fe2574514ba403c7"), entryChanges.Oid); + Assert.Equal(ChangeKind.Modified, entryChanges.Status); + Assert.Equal(file, entryChanges.OldPath); + Assert.Equal(Mode.NonExecutableFile, entryChanges.OldMode); + Assert.Equal(new ObjectId("7909961ae96accd75b6813d32e0fc1d6d52ec941"), entryChanges.OldOid); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/PatchStatsFixture.cs b/LibGit2Sharp.Tests/PatchStatsFixture.cs index 8b5ce40ee..758a08e2a 100644 --- a/LibGit2Sharp.Tests/PatchStatsFixture.cs +++ b/LibGit2Sharp.Tests/PatchStatsFixture.cs @@ -8,18 +8,20 @@ public class PatchStatsFixture : BaseFixture [Fact] public void CanExtractStatisticsFromDiff() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var oldTree = repo.Lookup("origin/packed-test").Tree; var newTree = repo.Lookup("HEAD").Tree; - var stats = repo.Diff.Compare(oldTree, newTree); + using (var stats = repo.Diff.Compare(oldTree, newTree)) + { + Assert.Equal(8, stats.TotalLinesAdded); + Assert.Equal(1, stats.TotalLinesDeleted); - Assert.Equal(8, stats.TotalLinesAdded); - Assert.Equal(1, stats.TotalLinesDeleted); - - var contentStats = stats["new.txt"]; - Assert.Equal(1, contentStats.LinesAdded); - Assert.Equal(1, contentStats.LinesDeleted); + var contentStats = stats["new.txt"]; + Assert.Equal(1, contentStats.LinesAdded); + Assert.Equal(1, contentStats.LinesDeleted); + } } } } diff --git a/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs b/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs index 6d2256554..a4bcec543 100644 --- a/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs +++ b/LibGit2Sharp.Tests/Properties/AssemblyInfo.cs @@ -1,39 +1,3 @@ -using System.Reflection; -using System.Runtime.InteropServices; +using Xunit; -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("libgit2sharp.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("libgit2sharp.Tests")] -[assembly: AssemblyCopyright("Copyright © 2010")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("808554a4-f9fd-4035-8ab9-325793c7da51")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: CollectionBehavior(DisableTestParallelization = true)] diff --git a/LibGit2Sharp.Tests/PushFixture.cs b/LibGit2Sharp.Tests/PushFixture.cs index 9f2a9e106..824c1d8c0 100644 --- a/LibGit2Sharp.Tests/PushFixture.cs +++ b/LibGit2Sharp.Tests/PushFixture.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.IO; using System.Linq; +using LibGit2Sharp.Handlers; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -9,15 +12,14 @@ public class PushFixture : BaseFixture { private void OnPushStatusError(PushStatusError pushStatusErrors) { - Assert.True(false, string.Format("Failed to update reference '{0}': {1}", - pushStatusErrors.Reference, pushStatusErrors.Message)); + Assert.Fail(string.Format("Failed to update reference '{0}': {1}", pushStatusErrors.Reference, pushStatusErrors.Message)); } private void AssertPush(Action push) { var scd = BuildSelfCleaningDirectory(); - string originalRepoPath = CloneBareTestRepo(); + string originalRepoPath = SandboxBareTestRepo(); string clonedRepoPath = Repository.Clone(originalRepoPath, scd.DirectoryPath); using (var originalRepo = new Repository(originalRepoPath)) @@ -35,7 +37,7 @@ private void AssertPush(Action push) // Change local state (commit) const string relativeFilepath = "new_file.txt"; Touch(clonedRepo.Info.WorkingDirectory, relativeFilepath, "__content__"); - clonedRepo.Index.Stage(relativeFilepath); + Commands.Stage(clonedRepo, relativeFilepath); clonedRepo.Commit("__commit_message__", Constants.Signature, Constants.Signature); // Assert local state has changed @@ -61,7 +63,7 @@ private void AssertPush(Action push) public void CanPushABranchTrackingAnUpstreamBranch() { bool packBuilderCalled = false; - Handlers.PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; AssertPush(repo => repo.Network.Push(repo.Head)); AssertPush(repo => repo.Network.Push(repo.Branches["master"])); @@ -76,6 +78,63 @@ public void CanPushABranchTrackingAnUpstreamBranch() Assert.True(packBuilderCalled); } + [Fact] + public void CanInvokePrePushCallbackAndSucceed() + { + bool packBuilderCalled = false; + bool prePushHandlerCalled = false; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; + PrePushHandler prePushHook = (IEnumerable updates) => + { + Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count()); + prePushHandlerCalled = true; + return true; + }; + + AssertPush(repo => repo.Network.Push(repo.Head)); + AssertPush(repo => repo.Network.Push(repo.Branches["master"])); + + PushOptions options = new PushOptions() + { + OnPushStatusError = OnPushStatusError, + OnPackBuilderProgress = packBuilderCb, + OnNegotiationCompletedBeforePush = prePushHook, + }; + + AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); + Assert.True(packBuilderCalled); + Assert.True(prePushHandlerCalled); + } + + [Fact] + public void CanInvokePrePushCallbackAndFail() + { + bool packBuilderCalled = false; + bool prePushHandlerCalled = false; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = true; return true; }; + PrePushHandler prePushHook = (IEnumerable updates) => + { + Assert.True(updates.Count() == 1, "Expected 1 update, received " + updates.Count()); + prePushHandlerCalled = true; + return false; + }; + + AssertPush(repo => repo.Network.Push(repo.Head)); + AssertPush(repo => repo.Network.Push(repo.Branches["master"])); + + PushOptions options = new PushOptions() + { + OnPushStatusError = OnPushStatusError, + OnPackBuilderProgress = packBuilderCb, + OnNegotiationCompletedBeforePush = prePushHook + }; + + Assert.Throws(() => { AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); }); + + Assert.False(packBuilderCalled); + Assert.True(prePushHandlerCalled); + } + [Fact] public void PushingABranchThatDoesNotTrackAnUpstreamBranchThrows() { @@ -96,7 +155,7 @@ public void CanForcePush() // Create a new repository string localRepoPath = InitNewRepository(); - using (var localRepo = new Repository(localRepoPath)) + using (var localRepo = new Repository(localRepoPath, new RepositoryOptions { Identity = Constants.Identity })) { // Add a commit Commit first = AddCommitToRepo(localRepo); @@ -122,22 +181,53 @@ public void CanForcePush() // Force push the new commit string pushRefSpec = string.Format("+{0}:{0}", localRepo.Head.CanonicalName); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + localRepo.Network.Push(localRepo.Network.Remotes.Single(), pushRefSpec); AssertRemoteHeadTipEquals(localRepo, second.Sha); AssertRefLogEntry(localRepo, "refs/remotes/origin/master", - localRepo.Head.Tip.Id, "update by push", - oldId); + "update by push", + oldId, localRepo.Head.Tip.Id, + Constants.Identity, before); } } + [Fact] + public void CanPushWithCustomHeaders() + { + const string knownHeader = "X-Hello: mygit-201"; + var options = new PushOptions { CustomHeaders = new string[] { knownHeader } }; + AssertPush(repo => + repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); + } + + [Fact] + public void CannotPushWithForbiddenCustomHeaders() + { + const string knownHeader = "User-Agent: mygit-201"; + var options = new PushOptions { CustomHeaders = new string[] { knownHeader } }; + Assert.Throws( + () => AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options))); + } + + [Fact] + public void CannotPushWithMalformedCustomHeaders() + { + const string knownHeader = "Hello world"; + var options = new PushOptions { CustomHeaders = new string[] { knownHeader } }; + Assert.Throws( + () => AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options))); + } + private static void AssertRemoteHeadTipEquals(IRepository localRepo, string sha) { var remoteReferences = localRepo.Network.ListReferences(localRepo.Network.Remotes.Single()); - DirectReference remoteHead = remoteReferences.Single(r => r.CanonicalName == "HEAD"); + Reference remoteHead = remoteReferences.Single(r => r.CanonicalName == "HEAD"); - Assert.Equal(sha, remoteHead.TargetIdentifier); + Assert.Equal(sha, remoteHead.ResolveToDirectReference().TargetIdentifier); } private void UpdateTheRemoteRepositoryWithANewCommit(string remoteRepoPath) @@ -159,12 +249,12 @@ private void UpdateTheRemoteRepositoryWithANewCommit(string remoteRepoPath) private Commit AddCommitToRepo(IRepository repository) { - string random = Guid.NewGuid().ToString(); + string random = Path.GetRandomFileName(); string filename = random + ".txt"; Touch(repository.Info.WorkingDirectory, filename, random); - repository.Index.Stage(filename); + Commands.Stage(repository, filename); return repository.Commit("New commit", Constants.Signature, Constants.Signature); } diff --git a/LibGit2Sharp.Tests/RebaseFixture.cs b/LibGit2Sharp.Tests/RebaseFixture.cs new file mode 100644 index 000000000..355e19295 --- /dev/null +++ b/LibGit2Sharp.Tests/RebaseFixture.cs @@ -0,0 +1,784 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class RebaseFixture : BaseFixture + { + const string masterBranch1Name = "M1"; + const string masterBranch2Name = "M2"; + const string topicBranch1Name = "T1"; + const string topicBranch2Name = "T2"; + const string conflictBranch1Name = "C1"; + const string topicBranch1PrimeName = "T1Prime"; + + string filePathA = "a.txt"; + string filePathB = "b.txt"; + string filePathC = "c.txt"; + string filePathD = "d.txt"; + + [Theory] + [InlineData(topicBranch2Name, topicBranch2Name, topicBranch1Name, masterBranch1Name, 3)] + [InlineData(topicBranch2Name, topicBranch2Name, topicBranch1Name, topicBranch1Name, 3)] + [InlineData(topicBranch2Name, topicBranch1Name, masterBranch2Name, masterBranch2Name, 3)] + [InlineData(topicBranch2Name, topicBranch1Name, masterBranch2Name, null, 3)] + [InlineData(topicBranch1Name, null, masterBranch2Name, null, 3)] + public void CanRebase(string initialBranchName, + string branchName, + string upstreamName, + string ontoName, + int stepCount) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, initialBranchName); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = (branchName == null) ? null : repo.Branches[branchName]; + Branch upstream = repo.Branches[upstreamName]; + Branch onto = (ontoName == null) ? null : repo.Branches[ontoName]; + Commit expectedSinceCommit = (branch == null) ? repo.Head.Tip : branch.Tip; + Commit expectedUntilCommit = upstream.Tip; + Commit expectedOntoCommit = (onto == null) ? upstream.Tip : onto.Tip; + + int beforeStepCallCount = 0; + int afterStepCallCount = 0; + bool beforeRebaseStepCountCorrect = true; + bool afterRebaseStepCountCorrect = true; + bool totalStepCountCorrect = true; + + List PreRebaseCommits = new List(); + List PostRebaseResults = new List(); + ObjectId expectedParentId = upstream.Tip.Id; + + RebaseOptions options = new RebaseOptions() + { + RebaseStepStarting = x => + { + beforeRebaseStepCountCorrect &= beforeStepCallCount == x.StepIndex; + totalStepCountCorrect &= (x.TotalStepCount == stepCount); + beforeStepCallCount++; + PreRebaseCommits.Add(x.StepInfo.Commit); + }, + RebaseStepCompleted = x => + { + afterRebaseStepCountCorrect &= (afterStepCallCount == x.CompletedStepIndex); + totalStepCountCorrect &= (x.TotalStepCount == stepCount); + afterStepCallCount++; + PostRebaseResults.Add(new CompletedRebaseStepInfo(x.Commit, x.WasPatchAlreadyApplied)); + }, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Validation: + Assert.True(afterRebaseStepCountCorrect, "Unexpected CompletedStepIndex value in RebaseStepCompleted"); + Assert.True(beforeRebaseStepCountCorrect, "Unexpected StepIndex value in RebaseStepStarting"); + Assert.True(totalStepCountCorrect, "Unexpected TotalStepcount value in Rebase step callback"); + Assert.Equal(RebaseStatus.Complete, rebaseResult.Status); + Assert.Equal(stepCount, rebaseResult.TotalStepCount); + Assert.Null(rebaseResult.CurrentStepInfo); + + Assert.Equal(stepCount, rebaseResult.CompletedStepCount); + Assert.False(repo.RetrieveStatus().IsDirty); + + Assert.Equal(stepCount, beforeStepCallCount); + Assert.Equal(stepCount, afterStepCallCount); + + // Verify the chain of source commits that were rebased. + CommitFilter sourceCommitFilter = new CommitFilter() + { + IncludeReachableFrom = expectedSinceCommit, + ExcludeReachableFrom = expectedUntilCommit, + SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological, + }; + Assert.Equal(repo.Commits.QueryBy(sourceCommitFilter), PreRebaseCommits); + + // Verify the chain of commits that resulted from the rebase. + Commit expectedParent = expectedOntoCommit; + foreach (CompletedRebaseStepInfo stepInfo in PostRebaseResults) + { + Commit rebasedCommit = stepInfo.Commit; + Assert.Equal(expectedParent.Id, rebasedCommit.Parents.First().Id); + Assert.False(stepInfo.WasPatchAlreadyApplied); + expectedParent = rebasedCommit; + } + + Assert.Equal(repo.Head.Tip, PostRebaseResults.Last().Commit); + } + } + + [Fact] + public void CanRebaseBranchOntoItself() + { + // Maybe we should have an "up-to-date" return type for scenarios such as these, + // but for now this test is to make sure we do something reasonable + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + Commands.Checkout(repo, topicBranch2Name); + Branch b = repo.Branches[topicBranch2Name]; + + RebaseResult result = repo.Rebase.Start(b, b, null, Constants.Identity, new RebaseOptions()); + Assert.Equal(0, result.TotalStepCount); + Assert.Equal(RebaseStatus.Complete, result.Status); + Assert.Equal(0, result.CompletedStepCount); + } + } + + private class CompletedRebaseStepInfo + { + public CompletedRebaseStepInfo(Commit commit, bool wasPatchAlreadyApplied) + { + Commit = commit; + WasPatchAlreadyApplied = wasPatchAlreadyApplied; + } + + public Commit Commit { get; set; } + + public bool WasPatchAlreadyApplied { get; set; } + + public override string ToString() + { + return string.Format("CompletedRebaseStepInfo: {0}", Commit); + } + } + + private class CompletedRebaseStepInfoEqualityComparer : IEqualityComparer + { + bool IEqualityComparer.Equals(CompletedRebaseStepInfo x, CompletedRebaseStepInfo y) + { + if (x == null && y == null) + { + return true; + } + + if ((x == null && y != null) || + (x != null && y == null)) + { + return false; + } + + return x.WasPatchAlreadyApplied == y.WasPatchAlreadyApplied && + ObjectId.Equals(x.Commit, y.Commit); + } + + int IEqualityComparer.GetHashCode(CompletedRebaseStepInfo obj) + { + int hashCode = obj.WasPatchAlreadyApplied.GetHashCode(); + + if (obj.Commit != null) + { + hashCode += obj.Commit.GetHashCode(); + } + + return hashCode; + } + } + + /// + /// Verify a single rebase, but in more detail. + /// + [Theory] + [InlineData("* text=auto", "\r\n", new[] { "2cad6e96a0028f1764dcbde6292a9a1471acb114", "18fd3deebe6124b5dacc8426d589d617a968e8d1", "048977d8cb90d530e83cc615a17a49f3068f68c1" })] + [InlineData("* text=auto", "\n", new[] { "2cad6e96a0028f1764dcbde6292a9a1471acb114", "18fd3deebe6124b5dacc8426d589d617a968e8d1", "048977d8cb90d530e83cc615a17a49f3068f68c1" })] + [InlineData("* text=auto\n*.txt eol=lf", "\n", new[] { "577d176b00a55e88e9b34da87e4357dfc9a486fd", "ea0ad4d8b500394a61874ebfda5904376e2b1098", "521b8383ca3fde9e369587492e7a3945677f1b2c" })] + [InlineData("* text=auto\r\n*.txt eol=crlf", "\r\n", new[] { "67d29fdf654ac4773c9405ab4b54aa7ff092f339", "7b70c02e175d378b44ea28aeeece775cd972047a", "81f203dbfe00a5c1ecd9c0e6b03705e6cffda5c0" })] + [InlineData("* binary", "\r\n", new[] { "f5a5ded935597108709224170accddc5aeb5c287", "518adb8bb1ea1058d1825d3fe08d27f80c0e829b", "d2db503ab553c970d34e1b5e3ff68768adef05bc" })] + [InlineData("* binary", "\n", new[] { "93a0e9680246d1f1e43fbd5308f7936424d9e81a", "5fd40bffbdd884632c330a254a2bd1dfaaaad3c1", "4df5c91b2d8318781b07d04f6bfa77304c372f1e" })] + public void VerifyRebaseDetailed(string attributes, string lineEnding, string[] expectedIds) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo, attributes, lineEnding); + + Branch initialBranch = repo.Branches[topicBranch1Name]; + Branch upstreamBranch = repo.Branches[masterBranch2Name]; + + Commands.Checkout(repo, initialBranch); + Assert.False(repo.RetrieveStatus().IsDirty); + + bool wasCheckoutProgressCalled = false; + bool wasCheckoutProgressCalledForResetingHead = false; + bool wasCheckoutNotifyCalled = false; + bool wasCheckoutNotifyCalledForResetingHead = false; + + bool startedApplyingSteps = false; + + RebaseOptions options = new RebaseOptions() + { + OnCheckoutProgress = (x, y, z) => + { + if (startedApplyingSteps) + { + wasCheckoutProgressCalled = true; + } + else + { + wasCheckoutProgressCalledForResetingHead = true; + } + }, + OnCheckoutNotify = (x, y) => + { + if (startedApplyingSteps) + { + wasCheckoutNotifyCalled = true; + } + else + { + wasCheckoutNotifyCalledForResetingHead = true; + } + + return true; + }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + + RebaseStepStarting = x => startedApplyingSteps = true, + + }; + + repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + + Assert.True(wasCheckoutNotifyCalledForResetingHead); + Assert.True(wasCheckoutProgressCalledForResetingHead); + Assert.True(wasCheckoutNotifyCalled); + Assert.True(wasCheckoutProgressCalled); + + // Verify the chain of resultant rebased commits. + CommitFilter commitFilter = new CommitFilter() + { + IncludeReachableFrom = repo.Head.Tip, + ExcludeReachableFrom = upstreamBranch.Tip, + SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological, + }; + + List expectedTreeIds = new List() + { + new ObjectId(expectedIds[0]), + new ObjectId(expectedIds[1]), + new ObjectId(expectedIds[2]), + }; + + List rebasedCommits = repo.Commits.QueryBy(commitFilter).ToList(); + + Assert.Equal(3, rebasedCommits.Count); + for (int i = 0; i < 3; i++) + { + Assert.Equal(expectedTreeIds[i], rebasedCommits[i].Tree.Id); + Assert.Equal(Constants.Signature.Name, rebasedCommits[i].Author.Name); + Assert.Equal(Constants.Signature.Email, rebasedCommits[i].Author.Email); + Assert.Equal(Constants.Signature2.Name, rebasedCommits[i].Committer.Name); + Assert.Equal(Constants.Signature2.Email, rebasedCommits[i].Committer.Email); + } + } + } + + [Fact] + public void CanContinueRebase() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + int beforeStepCallCount = 0; + int afterStepCallCount = 0; + bool wasCheckoutProgressCalled = false; + bool wasCheckoutNotifyCalled = false; + + RebaseOptions options = new RebaseOptions() + { + RebaseStepStarting = x => beforeStepCallCount++, + RebaseStepCompleted = x => afterStepCallCount++, + OnCheckoutProgress = (x, y, z) => wasCheckoutProgressCalled = true, + OnCheckoutNotify = (x, y) => { wasCheckoutNotifyCalled = true; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + // Verify that expected callbacks were called + Assert.Equal(1, beforeStepCallCount); + Assert.Equal(0, afterStepCallCount); + Assert.True(wasCheckoutProgressCalled, "CheckoutProgress callback was not called."); + + // Resolve the conflict + foreach (Conflict conflict in repo.Index.Conflicts) + { + Touch(repo.Info.WorkingDirectory, + conflict.Theirs.Path, + repo.Lookup(conflict.Theirs.Id).GetContentText(new FilteringOptions(conflict.Theirs.Path))); + Commands.Stage(repo, conflict.Theirs.Path); + } + + Assert.True(repo.Index.IsFullyMerged); + + // Clear the flags: + wasCheckoutProgressCalled = false; wasCheckoutNotifyCalled = false; + RebaseResult continuedRebaseResult = repo.Rebase.Continue(Constants.Identity, options); + + Assert.NotNull(continuedRebaseResult); + Assert.Equal(RebaseStatus.Complete, continuedRebaseResult.Status); + Assert.False(repo.RetrieveStatus().IsDirty); + Assert.True(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + Assert.Equal(3, beforeStepCallCount); + Assert.Equal(3, afterStepCallCount); + Assert.True(wasCheckoutProgressCalled, "CheckoutProgress callback was not called."); + Assert.True(wasCheckoutNotifyCalled, "CheckoutNotify callback was not called."); + } + } + + [Fact] + public void ContinuingRebaseWithUnstagedChangesThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, null)); + + // Resolve the conflict + foreach (Conflict conflict in repo.Index.Conflicts) + { + Touch(repo.Info.WorkingDirectory, + conflict.Theirs.Path, + repo.Lookup(conflict.Theirs.Id).GetContentText(new FilteringOptions(conflict.Theirs.Path))); + Commands.Stage(repo, conflict.Theirs.Path); + } + + Touch(repo.Info.WorkingDirectory, + filePathA, + "Unstaged content"); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, null)); + + Assert.True(repo.Index.IsFullyMerged); + } + } + + [Fact] + public void CanSpecifyFileConflictStrategy() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseOptions options = new RebaseOptions() + { + FileConflictStrategy = CheckoutFileConflictStrategy.Ours, + }; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, options); + + // Verify that we have a conflict. + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + string conflictFile = filePathB; + // Get the information on the conflict. + Conflict conflict = repo.Index.Conflicts[conflictFile]; + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + Blob expectedBlob = repo.Lookup(conflict.Ours.Id); + + // Check the content of the file on disk matches what is expected. + string expectedContent = expectedBlob.GetContentText(new FilteringOptions(conflictFile)); + Assert.Equal(expectedContent, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, conflictFile))); + } + } + + [Fact] + public void CanQueryRebaseOperation() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + RebaseStepInfo info = repo.Rebase.GetCurrentStepInfo(); + + Assert.Equal(0, repo.Rebase.GetCurrentStepIndex()); + Assert.Equal(3, repo.Rebase.GetTotalStepCount()); + Assert.Equal(RebaseStepOperation.Pick, info.Type); + } + } + + [Fact] + public void CanAbortRebase() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(0, rebaseResult.CompletedStepCount); + Assert.Equal(3, rebaseResult.TotalStepCount); + + // Set up the callbacks to verify that checkout progress / notify + // callbacks are called. + bool wasCheckoutProgressCalled = false; + bool wasCheckoutNotifyCalled = false; + RebaseOptions options = new RebaseOptions() + { + OnCheckoutProgress = (x, y, z) => wasCheckoutProgressCalled = true, + OnCheckoutNotify = (x, y) => { wasCheckoutNotifyCalled = true; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + repo.Rebase.Abort(options); + Assert.False(repo.RetrieveStatus().IsDirty, "Repository workdir is dirty after Rebase.Abort."); + Assert.True(repo.Index.IsFullyMerged, "Repository index is not fully merged after Rebase.Abort."); + Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); + + Assert.True(wasCheckoutProgressCalled, "Checkout progress callback was not called during Rebase.Abort."); + Assert.True(wasCheckoutNotifyCalled, "Checkout notify callback was not called during Rebase.Abort."); + } + } + + [Fact] + public void RebaseWhileAlreadyRebasingThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch branch = repo.Branches[topicBranch1Name]; + Branch upstream = repo.Branches[conflictBranch1Name]; + Branch onto = repo.Branches[conflictBranch1Name]; + + RebaseResult rebaseResult = repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null); + + // Verify that we have a conflict. + Assert.Equal(RebaseStatus.Conflicts, rebaseResult.Status); + Assert.True(repo.RetrieveStatus().IsDirty); + Assert.Equal(CurrentOperation.RebaseMerge, repo.Info.CurrentOperation); + + Assert.Throws(() => + repo.Rebase.Start(branch, upstream, onto, Constants.Identity, null)); + } + } + + [Fact] + public void RebaseOperationsWithoutRebasingThrow() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + + Commands.Checkout(repo, topicBranch1Name); + + Assert.Throws(() => + repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); + + Assert.Throws(() => + repo.Rebase.Abort()); + } + } + + [Fact] + public void CurrentStepInfoIsNullWhenNotRebasing() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo); + Commands.Checkout(repo, topicBranch1Name); + + Assert.Null(repo.Rebase.GetCurrentStepInfo()); + } + } + + [Theory] + [InlineData("* text=auto", "\r\n", "379e80ed7824be7672e1e30ddd8f44aa081d57d4")] + [InlineData("* text=auto", "\n", "379e80ed7824be7672e1e30ddd8f44aa081d57d4")] + [InlineData("* text=auto\n*.txt eol=lf", "\n", "94121eeebf7cfe0acf22425eab36fcdc737132b6")] + [InlineData("* text=auto\r\n*.txt eol=crlf", "\r\n", "dad06142cc632aea81cbc8486583011c4d622580")] + [InlineData("* binary", "\r\n", "44492d98b725189cfc0203d4192dfbb1fd34bf02")] + [InlineData("* binary", "\n", "f4b5b95de77f4cd97b4728617bae2dd8ba9af914")] + public void CanRebaseHandlePatchAlreadyApplied(string attributes, string lineEnding, string expectedShaText) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (Repository repo = new Repository(path)) + { + ConstructRebaseTestRepository(repo, attributes, lineEnding); + + Commands.Checkout(repo, topicBranch1Name); + + Branch topicBranch1Prime = repo.CreateBranch(topicBranch1PrimeName, masterBranch1Name); + + string newFileRelativePath = "new_file.txt"; + Touch(repo.Info.WorkingDirectory, newFileRelativePath, "New Content"); + Commands.Stage(repo, newFileRelativePath); + Commit commit = repo.Commit("new commit 1", Constants.Signature, Constants.Signature, new CommitOptions()); + + Commands.Checkout(repo, topicBranch1Prime); + var cherryPickResult = repo.CherryPick(commit, Constants.Signature2); + Assert.Equal(CherryPickStatus.CherryPicked, cherryPickResult.Status); + + string newFileRelativePath2 = "new_file_2.txt"; + Touch(repo.Info.WorkingDirectory, newFileRelativePath2, "New Content for path 2"); + Commands.Stage(repo, newFileRelativePath2); + repo.Commit("new commit 2", Constants.Signature, Constants.Signature, new CommitOptions()); + + Branch upstreamBranch = repo.Branches[topicBranch1Name]; + + List rebaseResults = new List(); + + RebaseOptions options = new RebaseOptions() + { + RebaseStepCompleted = x => + { + rebaseResults.Add(new CompletedRebaseStepInfo(x.Commit, x.WasPatchAlreadyApplied)); + } + }; + + repo.Rebase.Start(null, upstreamBranch, null, Constants.Identity2, options); + ObjectId secondCommitExpectedTreeId = new ObjectId(expectedShaText); + Signature secondCommitAuthorSignature = Constants.Signature; + Identity secondCommitCommiterIdentity = Constants.Identity2; + + Assert.Equal(2, rebaseResults.Count); + Assert.True(rebaseResults[0].WasPatchAlreadyApplied); + + Assert.False(rebaseResults[1].WasPatchAlreadyApplied); + Assert.NotNull(rebaseResults[1].Commit); + + // This is the expected tree ID of the new commit. + Assert.Equal(secondCommitExpectedTreeId, rebaseResults[1].Commit.Tree.Id); + Assert.True(Signature.Equals(secondCommitAuthorSignature, rebaseResults[1].Commit.Author)); + Assert.Equal(secondCommitCommiterIdentity.Name, rebaseResults[1].Commit.Committer.Name, StringComparer.Ordinal); + Assert.Equal(secondCommitCommiterIdentity.Email, rebaseResults[1].Commit.Committer.Email, StringComparer.Ordinal); + } + } + + [Fact] + public void RebasingInBareRepositoryThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Branch rebaseUpstreamBranch = repo.Branches["refs/heads/test"]; + + Assert.NotNull(rebaseUpstreamBranch); + Assert.Throws(() => repo.Rebase.Start(null, rebaseUpstreamBranch, null, Constants.Identity, new RebaseOptions())); + Assert.Throws(() => repo.Rebase.Continue(Constants.Identity, new RebaseOptions())); + Assert.Throws(() => repo.Rebase.Abort()); + } + } + + private void ConstructRebaseTestRepository(Repository repo, string attributes = "* text=auto", string lineEnding = "\r\n") + { + // Constructs a graph that looks like: + // * -- * -- * (modifications to c.txt) + // / | + // / T2 + // / + // * -- * -- * (modifications to b.txt) + // / | + // / T1 + // / + // *--*--*--*--*--*---- + // | | \ + // M1 M2 \ + // ---* + // | + // C1 + const string fileContentA1 = "A1"; + + const string fileContentB1 = "B1"; + const string fileContentB2 = "B2"; + const string fileContentB3 = "B3"; + const string fileContentB4 = "B4"; + + const string fileContentC1 = "C1"; + const string fileContentC2 = "C2"; + const string fileContentC3 = "C3"; + const string fileContentC4 = "C4"; + + const string fileContentD1 = "D1"; + const string fileContentD2 = "D2"; + const string fileContentD3 = "D3"; + + string workdir = repo.Info.WorkingDirectory; + Commit commit = null; + + CreateAttributesFile(repo, attributes); + + Commands.Stage(repo, ".gitattributes"); + commit = repo.Commit("setup", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathA, fileContentA1); + Commands.Stage(repo, filePathA); + commit = repo.Commit("commit 1", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, fileContentB1); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 2", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, fileContentC1); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 3", Constants.Signature, Constants.Signature, new CommitOptions()); + + Branch masterBranch1 = repo.CreateBranch(masterBranch1Name, commit); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 4", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2, fileContentB3)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 5", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2, fileContentB3, fileContentB4)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 6", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(topicBranch1Name, commit); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 7", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2, fileContentC3)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 8", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathC, string.Join(lineEnding, fileContentC1, fileContentC2, fileContentC3, fileContentC4)); + Commands.Stage(repo, filePathC); + commit = repo.Commit("commit 9", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(topicBranch2Name, commit); + + Commands.Checkout(repo, masterBranch1.Tip); + Touch(workdir, filePathD, fileContentD1); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 10", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathD, string.Join(lineEnding, fileContentD1, fileContentD2)); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 11", Constants.Signature, Constants.Signature, new CommitOptions()); + + Touch(workdir, filePathD, string.Join(lineEnding, fileContentD1, fileContentD2, fileContentD3)); + Commands.Stage(repo, filePathD); + commit = repo.Commit("commit 12", Constants.Signature, Constants.Signature, new CommitOptions()); + + repo.CreateBranch(masterBranch2Name, commit); + + // Create commit / branch that conflicts with T1 and T2 + Touch(workdir, filePathB, string.Join(lineEnding, fileContentB1, fileContentB2 + fileContentB3 + fileContentB4)); + Commands.Stage(repo, filePathB); + commit = repo.Commit("commit 13", Constants.Signature, Constants.Signature, new CommitOptions()); + repo.CreateBranch(conflictBranch1Name, commit); + } + } +} diff --git a/LibGit2Sharp.Tests/RefSpecFixture.cs b/LibGit2Sharp.Tests/RefSpecFixture.cs index 6e6cbd60f..e0639caa8 100644 --- a/LibGit2Sharp.Tests/RefSpecFixture.cs +++ b/LibGit2Sharp.Tests/RefSpecFixture.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections.Generic; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -11,19 +11,19 @@ public class RefSpecFixture : BaseFixture [Fact] public void CanCountRefSpecs() { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; - Assert.Equal(1, remote.RefSpecs.Count()); + Assert.Single(remote.RefSpecs); } } [Fact] public void CanIterateOverRefSpecs() { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; int count = 0; @@ -39,8 +39,8 @@ public void CanIterateOverRefSpecs() [Fact] public void FetchAndPushRefSpecsComposeRefSpecs() { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; @@ -53,8 +53,8 @@ public void FetchAndPushRefSpecsComposeRefSpecs() [Fact] public void CanReadRefSpecDetails() { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var remote = repo.Network.Remotes["origin"]; @@ -63,7 +63,7 @@ public void CanReadRefSpecDetails() Assert.Equal("refs/heads/*", refSpec.Source); Assert.Equal("refs/remotes/origin/*", refSpec.Destination); - Assert.Equal(true, refSpec.ForceUpdate); + Assert.True(refSpec.ForceUpdate); } } @@ -73,28 +73,33 @@ public void CanReadRefSpecDetails() [InlineData(new string[0], new string[] { "refs/ghi:refs/jkl/mno" })] public void CanReplaceRefSpecs(string[] newFetchRefSpecs, string[] newPushRefSpecs) { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - var oldRefSpecs = remote.RefSpecs.ToList(); - - var newRemote = repo.Network.Remotes.Update(remote, - r => r.FetchRefSpecs = newFetchRefSpecs, r => r.PushRefSpecs = newPushRefSpecs); - - Assert.Equal(oldRefSpecs, remote.RefSpecs.ToList()); + List oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.ToList(); - var actualNewFetchRefSpecs = newRemote.RefSpecs - .Where(s => s.Direction == RefSpecDirection.Fetch) - .Select(r => r.Specification) - .ToArray(); - Assert.Equal(newFetchRefSpecs, actualNewFetchRefSpecs); + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs = newFetchRefSpecs, r => r.PushRefSpecs = newPushRefSpecs); + Assert.Equal(oldRefSpecs, remote.RefSpecs.ToList()); + } - var actualNewPushRefSpecs = newRemote.RefSpecs - .Where(s => s.Direction == RefSpecDirection.Push) - .Select(r => r.Specification) - .ToArray(); - Assert.Equal(newPushRefSpecs, actualNewPushRefSpecs); + using (var newRemote = repo.Network.Remotes["origin"]) + { + var actualNewFetchRefSpecs = newRemote.RefSpecs + .Where(s => s.Direction == RefSpecDirection.Fetch) + .Select(r => r.Specification) + .ToArray(); + Assert.Equal(newFetchRefSpecs, actualNewFetchRefSpecs); + + var actualNewPushRefSpecs = newRemote.RefSpecs + .Where(s => s.Direction == RefSpecDirection.Push) + .Select(r => r.Specification) + .ToArray(); + Assert.Equal(newPushRefSpecs, actualNewPushRefSpecs); + } } } @@ -102,17 +107,16 @@ public void CanReplaceRefSpecs(string[] newFetchRefSpecs, string[] newPushRefSpe public void RemoteUpdaterSavesRefSpecsPermanently() { var fetchRefSpecs = new string[] { "refs/their/heads/*:refs/my/heads/*", "+refs/their/tag:refs/my/tag" }; + var path = SandboxStandardTestRepo(); - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs = fetchRefSpecs); + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); } - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) + using (var remote = repo.Network.Remotes["origin"]) { - var remote = repo.Network.Remotes["origin"]; var actualRefSpecs = remote.RefSpecs .Where(r => r.Direction == RefSpecDirection.Fetch) .Select(r => r.Specification) @@ -125,46 +129,52 @@ public void RemoteUpdaterSavesRefSpecsPermanently() public void CanAddAndRemoveRefSpecs() { string newRefSpec = "+refs/heads/test:refs/heads/other-test"; + var path = SandboxStandardTestRepo(); - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - - remote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Add(newRefSpec), r => r.PushRefSpecs.Add(newRefSpec)); - Assert.Contains(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); - Assert.Contains(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.Contains(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); + Assert.Contains(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + } - remote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Remove(newRefSpec), r => r.PushRefSpecs.Remove(newRefSpec)); - Assert.DoesNotContain(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); - Assert.DoesNotContain(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.DoesNotContain(newRefSpec, remote.FetchRefSpecs.Select(r => r.Specification)); + Assert.DoesNotContain(newRefSpec, remote.PushRefSpecs.Select(r => r.Specification)); + } } } [Fact] public void CanClearRefSpecs() { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; // Push refspec does not exist in cloned repository - remote = repo.Network.Remotes.Update(remote, r => r.PushRefSpecs.Add("+refs/test:refs/test")); + repo.Network.Remotes.Update("origin", r => r.PushRefSpecs.Add("+refs/test:refs/test")); - remote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Clear(), r => r.PushRefSpecs.Clear()); - Assert.Empty(remote.FetchRefSpecs); - Assert.Empty(remote.PushRefSpecs); - Assert.Empty(remote.RefSpecs); + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.Empty(remote.FetchRefSpecs); + Assert.Empty(remote.PushRefSpecs); + Assert.Empty(remote.RefSpecs); + } } } @@ -178,18 +188,56 @@ public void CanClearRefSpecs() [InlineData("refs/ whitespace:refs/test")] public void SettingInvalidRefSpecsThrows(string refSpec) { - var path = CloneStandardTestRepo(); - using (var repo = InitIsolatedRepository(path)) + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var remote = repo.Network.Remotes["origin"]; - var oldRefSpecs = remote.RefSpecs.Select(r => r.Specification).ToList(); + IEnumerable oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.Select(r => r.Specification).ToList(); + } - Assert.Throws(() => - repo.Network.Remotes.Update(remote, r => r.FetchRefSpecs.Add(refSpec))); + Assert.Throws(() => + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs.Add(refSpec))); var newRemote = repo.Network.Remotes["origin"]; Assert.Equal(oldRefSpecs, newRemote.RefSpecs.Select(r => r.Specification).ToList()); } } + + [Theory] + [InlineData("refs/heads/master", true, false)] + [InlineData("refs/heads/some/master", true, false)] + [InlineData("refs/remotes/foo/master", false, true)] + [InlineData("refs/tags/foo", false, false)] + public void CanCheckForMatches(string reference, bool shouldMatchSource, bool shouldMatchDest) + { + using (var repo = new Repository(InitNewRepository())) + { + var remote = repo.Network.Remotes.Add("foo", "blahblah", "refs/heads/*:refs/remotes/foo/*"); + var refspec = remote.RefSpecs.Single(); + + Assert.Equal(shouldMatchSource, refspec.SourceMatches(reference)); + Assert.Equal(shouldMatchDest, refspec.DestinationMatches(reference)); + } + } + + [Theory] + [InlineData("refs/heads/master", "refs/remotes/foo/master")] + [InlineData("refs/heads/bar/master", "refs/remotes/foo/bar/master")] + public void CanTransformRefspecs(string lhs, string rhs) + { + using (var repo = new Repository(InitNewRepository())) + { + var remote = repo.Network.Remotes.Add("foo", "blahblah", "refs/heads/*:refs/remotes/foo/*"); + var refspec = remote.RefSpecs.Single(); + + var actualTransformed = refspec.Transform(lhs); + var actualReverseTransformed = refspec.ReverseTransform(rhs); + + Assert.Equal(rhs, actualTransformed); + Assert.Equal(lhs, actualReverseTransformed); + } + } } } diff --git a/LibGit2Sharp.Tests/ReferenceFixture.cs b/LibGit2Sharp.Tests/ReferenceFixture.cs index c3c71a836..b4ec734d5 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -21,11 +21,13 @@ public void CanAddADirectReference() { const string name = "refs/heads/unit_test"; - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var newRef = (DirectReference)repo.Refs.Add(name, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); @@ -35,8 +37,9 @@ public void CanAddADirectReference() Assert.NotNull(repo.Refs[name]); AssertRefLogEntry(repo, name, - newRef.ResolveToDirectReference().Target.Id, - "branch: Created from be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + "branch: Created from be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + null, newRef.ResolveToDirectReference().Target.Id, Constants.Identity, before + ); } } @@ -46,12 +49,14 @@ public void CanAddADirectReferenceFromRevParseSpec() const string name = "refs/heads/extendedShaSyntaxRulz"; const string logMessage = "Create new ref"; - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); - var newRef = (DirectReference)repo.Refs.Add(name, "master^1^2", Constants.Signature, logMessage); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newRef = (DirectReference)repo.Refs.Add(name, "master^1^2", logMessage); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); Assert.NotNull(newRef.Target); @@ -59,9 +64,9 @@ public void CanAddADirectReferenceFromRevParseSpec() Assert.Equal(newRef.Target.Sha, newRef.TargetIdentifier); Assert.NotNull(repo.Refs[name]); - AssertRefLogEntry(repo, name, - newRef.ResolveToDirectReference().Target.Id, - logMessage, committer: Constants.Signature); + AssertRefLogEntry(repo, name, logMessage, + null, newRef.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } @@ -70,10 +75,10 @@ public void CreatingADirectReferenceWithARevparseSpecPointingAtAnUnknownObjectFa { const string name = "refs/heads/extendedShaSyntaxRulz"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Refs.Add(name, "master^42")); + Assert.Throws(() => repo.Refs.Add(name, "master^42")); } } @@ -83,7 +88,7 @@ public void CanAddASymbolicReferenceFromTheTargetName() const string name = "refs/heads/unit_test"; const string target = "refs/heads/master"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, target); @@ -99,14 +104,14 @@ public void CanAddASymbolicReferenceFromTheTargetReference() const string target = "refs/heads/master"; const string logMessage = "unit_test reference init"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { EnableRefLog(repo); var targetRef = repo.Refs[target]; - var newRef = repo.Refs.Add(name, targetRef, Constants.Signature, logMessage); + var newRef = repo.Refs.Add(name, targetRef, logMessage); AssertSymbolicRef(newRef, repo, target, name); Assert.Empty(repo.Refs.Log(newRef)); @@ -126,7 +131,7 @@ private static void AssertSymbolicRef(SymbolicReference newRef, IRepository repo [Fact] public void BlindlyCreatingADirectReferenceOverAnExistingOneThrows() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("refs/heads/master", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); @@ -136,7 +141,7 @@ public void BlindlyCreatingADirectReferenceOverAnExistingOneThrows() [Fact] public void BlindlyCreatingASymbolicReferenceOverAnExistingOneThrows() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("HEAD", "refs/heads/br2")); @@ -150,13 +155,16 @@ public void CanAddAndOverwriteADirectReference() const string target = "4c062a6361ae6959e06292c1fa5e2822d9c96345"; const string logMessage = "Create new ref"; - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); var oldRef = repo.Refs[name]; - var newRef = (DirectReference)repo.Refs.Add(name, target, Constants.Signature, logMessage, true); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newRef = (DirectReference)repo.Refs.Add(name, target, logMessage, true); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); Assert.NotNull(newRef.Target); @@ -164,9 +172,9 @@ public void CanAddAndOverwriteADirectReference() Assert.Equal(target, ((DirectReference)repo.Refs[name]).Target.Sha); AssertRefLogEntry(repo, name, - newRef.ResolveToDirectReference().Target.Id, logMessage, ((DirectReference)oldRef).Target.Id, - Constants.Signature); + newRef.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } @@ -177,30 +185,34 @@ public void CanAddAndOverwriteASymbolicReference() const string target = "refs/heads/br2"; const string logMessage = "Create new ref"; - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); var oldtarget = repo.Refs[name].ResolveToDirectReference().Target.Id; - var newRef = (SymbolicReference)repo.Refs.Add(name, target, Constants.Signature, logMessage, true); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newRef = (SymbolicReference)repo.Refs.Add(name, target, logMessage, true); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); Assert.NotNull(newRef.Target); Assert.Equal("a4a7dce85cf63874e984719f4fdd239f5145052f", newRef.ResolveToDirectReference().Target.Sha); Assert.Equal(target, ((SymbolicReference)repo.Refs.Head).Target.CanonicalName); - AssertRefLogEntry(repo, name, + AssertRefLogEntry(repo, name, logMessage, + oldtarget, newRef.ResolveToDirectReference().Target.Id, - logMessage, oldtarget, - Constants.Signature); + Constants.Identity, before); } } [Fact] public void AddWithEmptyStringForTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("refs/heads/newref", string.Empty)); } @@ -209,7 +221,8 @@ public void AddWithEmptyStringForTargetThrows() [Fact] public void AddWithEmptyStringThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add(string.Empty, "refs/heads/master")); } @@ -218,7 +231,8 @@ public void AddWithEmptyStringThrows() [Fact] public void AddWithNullForTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("refs/heads/newref", (string)null)); Assert.Throws(() => repo.Refs.Add("refs/heads/newref", (ObjectId)null)); @@ -228,7 +242,8 @@ public void AddWithNullForTargetThrows() [Fact] public void AddWithNullStringThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add(null, "refs/heads/master")); } @@ -237,7 +252,7 @@ public void AddWithNullStringThrows() [Fact] public void CanRemoveAReference() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Refs.Remove("refs/heads/packed"); @@ -247,7 +262,7 @@ public void CanRemoveAReference() [Fact] public void CanRemoveANonExistingReference() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string unknown = "refs/heads/dahlbyk/has/hawkeyes"; @@ -261,7 +276,7 @@ public void CanRemoveANonExistingReference() [Fact] public void ARemovedReferenceCannotBeLookedUp() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string refName = "refs/heads/test"; @@ -274,18 +289,18 @@ public void ARemovedReferenceCannotBeLookedUp() [Fact] public void RemovingAReferenceDecreasesTheRefsCount() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string refName = "refs/heads/test"; List refs = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.True(refs.Contains(refName)); + Assert.Contains(refName, refs); repo.Refs.Remove(refName); List refs2 = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.False(refs2.Contains(refName)); + Assert.DoesNotContain(refName, refs2); Assert.Equal(refs.Count - 1, refs2.Count); } @@ -294,7 +309,8 @@ public void RemovingAReferenceDecreasesTheRefsCount() [Fact] public void RemoveWithEmptyNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Remove(string.Empty)); } @@ -303,7 +319,8 @@ public void RemoveWithEmptyNameThrows() [Fact] public void RemoveWithNullNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Remove((string)null)); Assert.Throws(() => repo.Refs.Remove((Reference)null)); @@ -313,7 +330,7 @@ public void RemoveWithNullNameThrows() [Fact] public void CanListAllReferencesEvenCorruptedOnes() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { CreateCorruptedDeadBeefHead(repo.Info.Path); @@ -327,7 +344,8 @@ public void CanListAllReferencesEvenCorruptedOnes() [Fact] public void CanResolveHeadByName() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var head = (SymbolicReference)repo.Refs.Head; Assert.NotNull(head); @@ -348,7 +366,8 @@ public void CanResolveHeadByName() [Fact] public void CanResolveReferenceToALightweightTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var lwTag = (DirectReference)repo.Refs["refs/tags/lw"]; Assert.NotNull(lwTag); @@ -362,7 +381,8 @@ public void CanResolveReferenceToALightweightTag() [Fact] public void CanResolveReferenceToAnAnnotatedTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var annTag = (DirectReference)repo.Refs["refs/tags/test"]; Assert.NotNull(annTag); @@ -376,7 +396,8 @@ public void CanResolveReferenceToAnAnnotatedTag() [Fact] public void CanResolveRefsByName() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var master = (DirectReference)repo.Refs["refs/heads/master"]; Assert.NotNull(master); @@ -390,7 +411,8 @@ public void CanResolveRefsByName() [Fact] public void ResolvingWithEmptyStringThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => { Reference head = repo.Refs[string.Empty]; }); } @@ -399,7 +421,8 @@ public void ResolvingWithEmptyStringThrows() [Fact] public void ResolvingWithNullThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => { Reference head = repo.Refs[null]; }); } @@ -409,7 +432,7 @@ public void ResolvingWithNullThrows() public void CanUpdateTargetOfADirectReference() { const string masterRef = "refs/heads/master"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { string sha = repo.Refs["refs/heads/test"].ResolveToDirectReference().Target.Sha; @@ -429,14 +452,14 @@ public void CanUpdateTargetOfADirectReference() public void CanUpdateTargetOfADirectReferenceWithAnAbbreviatedSha() { const string masterRef = "refs/heads/master"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { string sha = repo.Refs["refs/heads/test"].ResolveToDirectReference().Target.Sha; Reference master = repo.Refs[masterRef]; Assert.NotEqual(sha, master.ResolveToDirectReference().Target.Sha); - Reference updated = repo.Refs.UpdateTarget(masterRef, sha.Substring(0,4)); + Reference updated = repo.Refs.UpdateTarget(masterRef, sha.Substring(0, 4)); master = repo.Refs[masterRef]; Assert.Equal(updated, master); @@ -449,7 +472,7 @@ public void CanUpdateTargetOfADirectReferenceWithAnAbbreviatedSha() public void CanUpdateTargetOfASymbolicReference() { const string name = "refs/heads/unit_test"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, "refs/heads/master"); @@ -467,7 +490,7 @@ public void CanUpdateTargetOfASymbolicReference() [Fact] public void CanUpdateHeadWithARevparseSpec() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Branch test = repo.Branches["test"]; @@ -485,55 +508,62 @@ public void CanUpdateHeadWithARevparseSpec() [Fact] public void CanUpdateHeadWithEitherAnObjectIdOrAReference() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); Reference head = repo.Refs.Head; Reference test = repo.Refs["refs/heads/test"]; - Reference direct = repo.Refs.UpdateTarget(head, new ObjectId(test.TargetIdentifier), Constants.Signature, null); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + Reference direct = repo.Refs.UpdateTarget(head, new ObjectId(test.TargetIdentifier), null); Assert.True((direct is DirectReference)); Assert.Equal(test.TargetIdentifier, direct.TargetIdentifier); Assert.Equal(repo.Refs.Head, direct); var testTargetId = test.ResolveToDirectReference().Target.Id; - AssertRefLogEntry(repo, "HEAD", - testTargetId, - null, + AssertRefLogEntry(repo, "HEAD", null, head.ResolveToDirectReference().Target.Id, - Constants.Signature); + testTargetId, + Constants.Identity, before); const string secondLogMessage = "second update target message"; - Reference symref = repo.Refs.UpdateTarget(head, test, Constants.Signature, secondLogMessage); + + before = DateTimeOffset.Now.TruncateMilliseconds(); + + Reference symref = repo.Refs.UpdateTarget(head, test, secondLogMessage); Assert.True((symref is SymbolicReference)); Assert.Equal(test.CanonicalName, symref.TargetIdentifier); Assert.Equal(repo.Refs.Head, symref); AssertRefLogEntry(repo, "HEAD", - testTargetId, secondLogMessage, testTargetId, - Constants.Signature); + testTargetId, + Constants.Identity, before); } } [Fact] public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); const string name = "refs/heads/master"; - var master = (DirectReference) repo.Refs[name]; + var master = (DirectReference)repo.Refs[name]; var @from = master.Target.Id; const string logMessage = "update target message"; - var newRef = (DirectReference)repo.Refs.UpdateTarget(master, "master^1^2", Constants.Signature, logMessage); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newRef = (DirectReference)repo.Refs.UpdateTarget(master, "master^1^2", logMessage); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); Assert.NotNull(newRef.Target); @@ -542,10 +572,10 @@ public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() Assert.NotNull(repo.Refs[name]); AssertRefLogEntry(repo, name, - newRef.Target.Id, logMessage, @from, - Constants.Signature); + newRef.Target.Id, + Constants.Identity, before); } } @@ -553,7 +583,7 @@ public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() public void UpdatingADirectRefWithSymbolFails() { const string name = "refs/heads/unit_test"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, "refs/heads/master"); @@ -570,7 +600,7 @@ public void UpdatingADirectRefWithSymbolFails() public void CanUpdateTargetOfADirectReferenceWithAShortReferenceNameAsARevparseSpec() { const string masterRef = "refs/heads/master"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Reference updatedMaster = repo.Refs.UpdateTarget(masterRef, "heads/test"); @@ -581,7 +611,8 @@ public void CanUpdateTargetOfADirectReferenceWithAShortReferenceNameAsARevparseS [Fact] public void UpdatingAReferenceTargetWithBadParametersFails() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.UpdateTarget(string.Empty, "refs/heads/packed")); Assert.Throws(() => repo.Refs.UpdateTarget("refs/heads/master", string.Empty)); @@ -594,16 +625,17 @@ public void UpdatingAReferenceTargetWithBadParametersFails() [Fact] public void UpdatingADirectReferenceTargetWithARevparsePointingAtAnUnknownObjectFails() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Refs.UpdateTarget(repo.Refs["refs/heads/master"], "refs/heads/nope")); + Assert.Throws(() => repo.Refs.UpdateTarget(repo.Refs["refs/heads/master"], "refs/heads/nope")); } } [Fact] public void CanRenameAReferenceToADeeperReferenceHierarchy() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string newName = "refs/tags/test/deep"; @@ -617,7 +649,7 @@ public void CanRenameAReferenceToADeeperReferenceHierarchy() [Fact] public void CanRenameAReferenceToAUpperReferenceHierarchy() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string newName = "refs/heads/o/sole"; @@ -633,8 +665,8 @@ public void CanRenameAReferenceToAUpperReferenceHierarchy() [Fact] public void CanRenameAReferenceToADifferentReferenceHierarchy() { - string path = CloneBareTestRepo(); - using (var repo = new Repository(path)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { const string oldName = "refs/tags/test"; const string newName = "refs/atic/tagtest"; @@ -643,20 +675,26 @@ public void CanRenameAReferenceToADifferentReferenceHierarchy() var oldId = repo.Refs[oldName].ResolveToDirectReference().Target.Id; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Reference renamed = repo.Refs.Rename(oldName, newName); Assert.NotNull(renamed); Assert.Equal(newName, renamed.CanonicalName); Assert.Equal(oldId, renamed.ResolveToDirectReference().Target.Id); - AssertRefLogEntry(repo, newName, renamed.ResolveToDirectReference().Target.Id, - string.Format("reference: renamed {0} to {1}", oldName, newName)); + AssertRefLogEntry(repo, newName, + string.Format("reference: renamed {0} to {1}", oldName, newName), + oldId, + renamed.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } [Fact] public void RenamingANonExistingReferenceThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Rename("refs/tags/i-am-void", "refs/atic/tagtest")); } @@ -665,7 +703,7 @@ public void RenamingANonExistingReferenceThrows() [Fact] public void CanRenameAndOverWriteAExistingReference() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string oldName = "refs/heads/packed"; @@ -681,7 +719,8 @@ public void CanRenameAndOverWriteAExistingReference() [Fact] public void BlindlyOverwritingAExistingReferenceThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Rename("refs/heads/packed", "refs/heads/br2")); } @@ -690,20 +729,20 @@ public void BlindlyOverwritingAExistingReferenceThrows() [Fact] public void RenamingAReferenceDoesNotDecreaseTheRefsCount() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string oldName = "refs/tags/test"; const string newName = "refs/atic/tagtest"; List refs = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.True(refs.Contains(oldName)); + Assert.Contains(oldName, refs); repo.Refs.Rename(oldName, newName); List refs2 = repo.Refs.Select(r => r.CanonicalName).ToList(); - Assert.False(refs2.Contains(oldName)); - Assert.True(refs2.Contains(newName)); + Assert.DoesNotContain(oldName, refs2); + Assert.Contains(newName, refs2); Assert.Equal(refs2.Count, refs.Count); } @@ -712,7 +751,7 @@ public void RenamingAReferenceDoesNotDecreaseTheRefsCount() [Fact] public void CanLookupARenamedReference() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string oldName = "refs/tags/test"; @@ -728,13 +767,14 @@ public void CanLookupARenamedReference() [Fact] public void CanFilterReferencesWithAGlob() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Equal(13, repo.Refs.FromGlob("*").Count()); Assert.Equal(5, repo.Refs.FromGlob("refs/heads/*").Count()); Assert.Equal(5, repo.Refs.FromGlob("refs/tags/*").Count()); Assert.Equal(3, repo.Refs.FromGlob("*t?[pqrs]t*").Count()); - Assert.Equal(0, repo.Refs.FromGlob("test").Count()); + Assert.Empty(repo.Refs.FromGlob("test")); } } @@ -750,17 +790,13 @@ public void CanFilterReferencesWithAGlob() [InlineData("/", false)] public void CanTellIfAReferenceIsValid(string refname, bool expectedResult) { - using (var repo = new Repository(BareTestRepoPath)) - { - Assert.Equal(expectedResult, Reference.IsValidName(refname)); - } + Assert.Equal(expectedResult, Reference.IsValidName(refname)); } [Fact] public void CanUpdateTheTargetOfASymbolicReferenceWithAnotherSymbolicReference() { - string repoPath = CloneBareTestRepo(); - + string repoPath = SandboxBareTestRepo(); using (var repo = new Repository(repoPath)) { Reference symbolicRef = repo.Refs.Add("refs/heads/unit_test", "refs/heads/master"); @@ -775,7 +811,8 @@ public void CanUpdateTheTargetOfASymbolicReferenceWithAnotherSymbolicReference() [Fact] public void LookingForLowerCaseHeadThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs["head"]); } @@ -789,23 +826,26 @@ private static T[] SortedRefs(IRepository repo, Func selector) [Fact] public void CanIdentifyReferenceKind() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Assert.True(repo.Refs["refs/heads/master"].IsLocalBranch()); - Assert.True(repo.Refs["refs/remotes/origin/master"].IsRemoteTrackingBranch()); - Assert.True(repo.Refs["refs/tags/lw"].IsTag()); + Assert.True(repo.Refs["refs/heads/master"].IsLocalBranch); + Assert.True(repo.Refs["refs/remotes/origin/master"].IsRemoteTrackingBranch); + Assert.True(repo.Refs["refs/tags/lw"].IsTag); } - using (var repo = new Repository(BareTestRepoPath)) + path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.True(repo.Refs["refs/notes/commits"].IsNote()); + Assert.True(repo.Refs["refs/notes/commits"].IsNote); } } [Fact] public void CanQueryReachability() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var result = repo.Refs.ReachableFrom( new[] { repo.Lookup("f8d44d7"), repo.Lookup("6dcf9bf") }); @@ -827,10 +867,11 @@ public void CanQueryReachability() [Fact] public void CanQueryReachabilityAmongASubsetOfreferences() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var result = repo.Refs.ReachableFrom( - repo.Refs.Where(r => r.IsTag()), + repo.Refs.Where(r => r.IsTag), new[] { repo.Lookup("f8d44d7"), repo.Lookup("6dcf9bf") }); var expected = new[] @@ -847,12 +888,13 @@ public void CanQueryReachabilityAmongASubsetOfreferences() [Fact] public void CanHandleInvalidArguments() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.ReachableFrom(null)); Assert.Throws(() => repo.Refs.ReachableFrom(null, repo.Commits.Take(2))); Assert.Throws(() => repo.Refs.ReachableFrom(repo.Refs, null)); - Assert.Empty(repo.Refs.ReachableFrom(new Commit[] { })); + Assert.Empty(repo.Refs.ReachableFrom(Array.Empty())); } } } diff --git a/LibGit2Sharp.Tests/ReflogFixture.cs b/LibGit2Sharp.Tests/ReflogFixture.cs index f8bc249d1..52973454b 100644 --- a/LibGit2Sharp.Tests/ReflogFixture.cs +++ b/LibGit2Sharp.Tests/ReflogFixture.cs @@ -1,8 +1,8 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -13,16 +13,16 @@ public void CanReadReflog() { const int expectedReflogEntriesCount = 3; - - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var reflog = repo.Refs.Log(repo.Refs.Head); Assert.Equal(expectedReflogEntriesCount, reflog.Count()); // Initial commit assertions - Assert.Equal("timothy.clem@gmail.com", reflog.Last().Commiter.Email); - Assert.True(reflog.Last().Message.StartsWith("clone: from")); + Assert.Equal("timothy.clem@gmail.com", reflog.Last().Committer.Email); + Assert.StartsWith("clone: from", reflog.Last().Message); Assert.Equal(ObjectId.Zero, reflog.Last().From); // second commit assertions @@ -34,7 +34,8 @@ public void CanReadReflog() [Fact] public void ReflogOfUnbornReferenceIsEmpty() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Empty(repo.Refs.Log("refs/heads/toto")); } @@ -43,7 +44,8 @@ public void ReflogOfUnbornReferenceIsEmpty() [Fact] public void ReadingReflogOfInvalidReferenceNameThrows() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Log("toto").Count()); } @@ -54,7 +56,9 @@ public void CommitShouldCreateReflogEntryOnHeadAndOnTargetedDirectReference() { string repoPath = InitNewRepository(); - using (var repo = new Repository(repoPath)) + var identity = Constants.Identity; + + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = identity })) { // setup refs as HEAD => unit_test => master var newRef = repo.Refs.Add("refs/heads/unit_test", "refs/heads/master"); @@ -63,28 +67,46 @@ public void CommitShouldCreateReflogEntryOnHeadAndOnTargetedDirectReference() const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "Hope reflog behaves as it should"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + Commit commit = repo.Commit(commitMessage, author, author); // Assert a reflog entry is created on HEAD - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); var reflogEntry = repo.Refs.Log("HEAD").First(); - Assert.Equal(author, reflogEntry.Commiter); + + Assert.Equal(identity.Name, reflogEntry.Committer.Name); + Assert.Equal(identity.Email, reflogEntry.Committer.Email); + + // When verifying the timestamp range, give a little more room on the range. + // Git or file system datetime truncation seems to cause these stamps to jump up to a second earlier + // than we expect. See https://github.com/libgit2/libgit2sharp/issues/1764 + var low = before - TimeSpan.FromSeconds(1); + var high = DateTimeOffset.Now.TruncateMilliseconds() + TimeSpan.FromSeconds(1); + Assert.InRange(reflogEntry.Committer.When, low, high); + Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); // Assert the same reflog entry is created on refs/heads/master - Assert.Equal(1, repo.Refs.Log("refs/heads/master").Count()); + Assert.Single(repo.Refs.Log("refs/heads/master")); reflogEntry = repo.Refs.Log("HEAD").First(); - Assert.Equal(author, reflogEntry.Commiter); + + Assert.Equal(identity.Name, reflogEntry.Committer.Name); + Assert.Equal(identity.Email, reflogEntry.Committer.Email); + + Assert.InRange(reflogEntry.Committer.When, low, high); + Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(ObjectId.Zero, reflogEntry.From); // Assert no reflog entry is created on refs/heads/unit_test - Assert.Equal(0, repo.Refs.Log("refs/heads/unit_test").Count()); + Assert.Empty(repo.Refs.Log("refs/heads/unit_test")); } } @@ -97,14 +119,14 @@ public void CommitOnUnbornReferenceShouldCreateReflogEntryWithInitialTag() { const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "First commit should be logged as initial"; repo.Commit(commitMessage, author, author); // Assert the reflog entry message is correct - Assert.Equal(1, repo.Refs.Log("HEAD").Count()); + Assert.Single(repo.Refs.Log("HEAD")); Assert.Equal(string.Format("commit (initial): {0}", commitMessage), repo.Refs.Log("HEAD").First().Message); } } @@ -112,27 +134,42 @@ public void CommitOnUnbornReferenceShouldCreateReflogEntryWithInitialTag() [Fact] public void CommitOnDetachedHeadShouldInsertReflogEntry() { - string repoPath = CloneStandardTestRepo(); + string repoPath = SandboxStandardTestRepo(); - using (var repo = new Repository(repoPath)) + var identity = Constants.Identity; + + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = identity })) { Assert.False(repo.Info.IsHeadDetached); var parentCommit = repo.Head.Tip.Parents.First(); - repo.Checkout(parentCommit.Sha); + Commands.Checkout(repo, parentCommit.Sha); Assert.True(repo.Info.IsHeadDetached); const string relativeFilepath = "new.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); var author = Constants.Signature; const string commitMessage = "Commit on detached head"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var commit = repo.Commit(commitMessage, author, author); // Assert a reflog entry is created on HEAD var reflogEntry = repo.Refs.Log("HEAD").First(); - Assert.Equal(author, reflogEntry.Commiter); + + Assert.Equal(identity.Name, reflogEntry.Committer.Name); + Assert.Equal(identity.Email, reflogEntry.Committer.Email); + + // When verifying the timestamp range, give a little more room on the range. + // Git or file system datetime truncation seems to cause these stamps to jump up to a second earlier + // than we expect. See https://github.com/libgit2/libgit2sharp/issues/1764 + var low = before - TimeSpan.FromSeconds(1); + var high = DateTimeOffset.Now.TruncateMilliseconds() + TimeSpan.FromSeconds(1); + Assert.InRange(reflogEntry.Committer.When, low, high); + Assert.Equal(commit.Id, reflogEntry.To); Assert.Equal(string.Format("commit: {0}", commitMessage), repo.Refs.Log("HEAD").First().Message); } @@ -172,7 +209,7 @@ public void AppendingToReflogDependsOnCoreLogAllRefUpdatesSetting(bool isBare, b public void UnsignedMethodsWriteCorrectlyToTheReflog() { var repoPath = InitNewRepository(true); - using (var repo = new Repository(repoPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) { EnableRefLog(repo); @@ -181,8 +218,11 @@ public void UnsignedMethodsWriteCorrectlyToTheReflog() var commit = repo.ObjectDatabase.CreateCommit(Constants.Signature, Constants.Signature, "yoink", tree, Enumerable.Empty(), false); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + var direct = repo.Refs.Add("refs/heads/direct", commit.Id); - AssertRefLogEntry(repo, direct.CanonicalName, direct.ResolveToDirectReference().Target.Id, null); + AssertRefLogEntry(repo, direct.CanonicalName, null, null, + direct.ResolveToDirectReference().Target.Id, Constants.Identity, before); var symbolic = repo.Refs.Add("refs/heads/symbolic", direct); Assert.Empty(repo.Refs.Log(symbolic)); // creation of symbolic refs doesn't update the reflog diff --git a/LibGit2Sharp.Tests/RemoteFixture.cs b/LibGit2Sharp.Tests/RemoteFixture.cs index 0299860a3..36990bb6e 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -10,7 +10,8 @@ public class RemoteFixture : BaseFixture [Fact] public void CanGetRemoteOrigin() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Remote origin = repo.Network.Remotes["origin"]; Assert.NotNull(origin); @@ -23,7 +24,8 @@ public void CanGetRemoteOrigin() [Fact] public void GettingRemoteThatDoesntExistReturnsNull() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Network.Remotes["test"]); } @@ -32,7 +34,8 @@ public void GettingRemoteThatDoesntExistReturnsNull() [Fact] public void CanEnumerateTheRemotes() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { int count = 0; @@ -53,7 +56,7 @@ public void CanEnumerateTheRemotes() [InlineData(TagFetchMode.None)] public void CanSetTagFetchMode(TagFetchMode tagFetchMode) { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string name = "upstream"; @@ -63,17 +66,20 @@ public void CanSetTagFetchMode(TagFetchMode tagFetchMode) Remote remote = repo.Network.Remotes[name]; Assert.NotNull(remote); - Remote updatedremote = repo.Network.Remotes.Update(remote, + repo.Network.Remotes.Update(name, r => r.TagFetchMode = tagFetchMode); - Assert.Equal(tagFetchMode, updatedremote.TagFetchMode); + using (var updatedremote = repo.Network.Remotes[name]) + { + Assert.Equal(tagFetchMode, updatedremote.TagFetchMode); + } } } [Fact] public void CanSetRemoteUrl() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string name = "upstream"; @@ -84,39 +90,50 @@ public void CanSetRemoteUrl() Remote remote = repo.Network.Remotes[name]; Assert.NotNull(remote); - Remote updatedremote = repo.Network.Remotes.Update(remote, - r => r.Url = newUrl); + repo.Network.Remotes.Update(name, r => r.Url = newUrl); - Assert.Equal(newUrl, updatedremote.Url); + using (var updatedremote = repo.Network.Remotes[name]) + { + Assert.Equal(newUrl, updatedremote.Url); + // with no push url set, PushUrl defaults to the fetch url + Assert.Equal(newUrl, updatedremote.PushUrl); + } } } [Fact] - public void CanCheckEqualityOfRemote() + public void CanSetRemotePushUrl() { - string path = CloneStandardTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { - Remote oneOrigin = repo.Network.Remotes["origin"]; - Assert.NotNull(oneOrigin); + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; + const string pushurl = "https://github.com/libgit2/libgit2.git"; - Remote otherOrigin = repo.Network.Remotes["origin"]; - Assert.Equal(oneOrigin, otherOrigin); + repo.Network.Remotes.Add(name, url); + Remote remote = repo.Network.Remotes[name]; + Assert.NotNull(remote); - Remote createdRemote = repo.Network.Remotes.Add("origin2", oneOrigin.Url); + // before setting push, both push and fetch urls should match + Assert.Equal(url, remote.Url); + Assert.Equal(url, remote.PushUrl); - Remote loadedRemote = repo.Network.Remotes["origin2"]; - Assert.NotNull(loadedRemote); - Assert.Equal(createdRemote, loadedRemote); + repo.Network.Remotes.Update(name, r => r.PushUrl = pushurl); - Assert.NotEqual(oneOrigin, loadedRemote); + using (var updatedremote = repo.Network.Remotes[name]) + { + // url should not change, push url should be set to new value + Assert.Equal(url, updatedremote.Url); + Assert.Equal(pushurl, updatedremote.PushUrl); + } } } [Fact] public void CreatingANewRemoteAddsADefaultRefSpec() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string name = "upstream"; @@ -139,7 +156,7 @@ public void CreatingANewRemoteAddsADefaultRefSpec() [Fact] public void CanAddANewRemoteWithAFetchRefSpec() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string name = "pull-requests"; @@ -160,7 +177,8 @@ public void CanAddANewRemoteWithAFetchRefSpec() [InlineData("/")] public void AddingARemoteWithAnInvalidNameThrows(string name) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string url = "https://github.com/libgit2/libgit2sharp.git"; @@ -180,11 +198,12 @@ public void CanTellIfARemoteNameIsValid(string refname, bool expectedResult) [Fact] public void DoesNotThrowWhenARemoteHasNoUrlSet() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { var noUrlRemote = repo.Network.Remotes["no_url"]; Assert.NotNull(noUrlRemote); - Assert.Equal(null, noUrlRemote.Url); + Assert.Null(noUrlRemote.Url); var remotes = repo.Network.Remotes.ToList(); Assert.Equal(1, remotes.Count(r => r.Name == "no_url")); @@ -194,7 +213,7 @@ public void DoesNotThrowWhenARemoteHasNoUrlSet() [Fact] public void CreatingARemoteAddsADefaultFetchRefSpec() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var remote = repo.Network.Remotes.Add("one", "http://github.com/up/stream"); @@ -205,7 +224,7 @@ public void CreatingARemoteAddsADefaultFetchRefSpec() [Fact] public void CanCreateARemoteWithASpecifiedFetchRefSpec() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var remote = repo.Network.Remotes.Add("two", "http://github.com/up/stream", "+refs/heads/*:refs/remotes/grmpf/*"); @@ -216,7 +235,7 @@ public void CanCreateARemoteWithASpecifiedFetchRefSpec() [Fact] public void CanDeleteExistingRemote() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.NotNull(repo.Network.Remotes["origin"]); @@ -231,7 +250,8 @@ public void CanDeleteExistingRemote() [Fact] public void CanDeleteNonExistingRemote() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Network.Remotes["i_dont_exist"]); repo.Network.Remotes.Remove("i_dont_exist"); @@ -241,7 +261,7 @@ public void CanDeleteNonExistingRemote() [Fact] public void CanRenameExistingRemote() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.NotNull(repo.Network.Remotes["origin"]); @@ -259,27 +279,27 @@ public void CanRenameExistingRemote() } [Fact] - public void CanRenameNonExistingRemote() + public void RenamingNonExistingRemoteThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Null(repo.Network.Remotes["i_dont_exist"]); - - repo.Network.Remotes.Rename("i_dont_exist", "i_dont_either", problem => Assert.True(false)); - Assert.Null(repo.Network.Remotes["i_dont_either"]); + Assert.Throws(() => + { + repo.Network.Remotes.Rename("i_dont_exist", "i_dont_either"); + }); } } [Fact] public void ReportsRemotesWithNonDefaultRefSpecs() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.NotNull(repo.Network.Remotes["origin"]); - repo.Network.Remotes.Update( - repo.Network.Remotes["origin"], + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = new[] { "+refs/heads/*:refs/remotes/upstream/*" }); repo.Network.Remotes.Rename("origin", "nondefault", problem => Assert.Equal("+refs/heads/*:refs/remotes/upstream/*", problem)); @@ -295,7 +315,7 @@ public void ReportsRemotesWithNonDefaultRefSpecs() [Fact] public void DoesNotReportRemotesWithAlreadyExistingRefSpec() { - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.NotNull(repo.Network.Remotes["origin"]); @@ -318,7 +338,7 @@ public void CanNotRenameWhenRemoteWithSameNameExists() const string name = "upstream"; const string url = "https://github.com/libgit2/libgit2sharp.git"; - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.NotNull(repo.Network.Remotes["origin"]); @@ -329,18 +349,40 @@ public void CanNotRenameWhenRemoteWithSameNameExists() } [Theory] - [InlineData("git@github.com:org/repo.git", false)] - [InlineData("git://site.com:80/org/repo.git", true)] - [InlineData("ssh://user@host.com:80/org/repo.git", false)] - [InlineData("http://site.com:80/org/repo.git", true)] - [InlineData("https://github.com:80/org/repo.git", true)] - [InlineData("ftp://site.com:80/org/repo.git", false)] - [InlineData("ftps://site.com:80/org/repo.git", false)] - [InlineData("file:///path/repo.git", true)] - [InlineData("protocol://blah.meh/whatever.git", false)] - public void CanCheckIfUrlisSupported(string url, bool supported) + [InlineData(null, null, false)] + [InlineData(null, false, false)] + [InlineData(null, true, true)] + [InlineData(false, null, false)] + [InlineData(false, false, false)] + [InlineData(false, true, true)] + [InlineData(true, null, true)] + [InlineData(true, false, false)] + [InlineData(true, true, true)] + public void ShoudlPruneOnFetchReflectsTheConfiguredSetting(bool? fetchPrune, bool? remotePrune, bool expectedFetchPrune) { - Assert.Equal(supported, Remote.IsSupportedUrl(url)); + var path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + Assert.Null(repo.Config.Get("fetch.prune")); + Assert.Null(repo.Config.Get("remote.origin.prune")); + + SetIfNotNull(repo, "fetch.prune", fetchPrune); + SetIfNotNull(repo, "remote.origin.prune", remotePrune); + + var remote = repo.Network.Remotes["origin"]; + Assert.Equal(expectedFetchPrune, remote.AutomaticallyPruneOnFetch); + } + } + + private void SetIfNotNull(IRepository repo, string configName, bool? value) + { + if (!value.HasValue) + { + return; + } + + repo.Config.Set(configName, value.Value); } } } diff --git a/LibGit2Sharp.Tests/RemoveFixture.cs b/LibGit2Sharp.Tests/RemoveFixture.cs index 04d12feec..1b74995ca 100644 --- a/LibGit2Sharp.Tests/RemoveFixture.cs +++ b/LibGit2Sharp.Tests/RemoveFixture.cs @@ -14,61 +14,61 @@ public class RemoveFixture : BaseFixture * 'git rm --cached ' works (file removed only from index). * 'git rm ' works (file removed from both index and workdir). */ - [InlineData(false, "1/branch_file.txt", false, FileStatus.Unaltered, true, true, FileStatus.Untracked | FileStatus.Removed)] - [InlineData(true, "1/branch_file.txt", false, FileStatus.Unaltered, true, false, FileStatus.Removed)] + [InlineData(false, "1/branch_file.txt", false, FileStatus.Unaltered, true, true, FileStatus.NewInWorkdir | FileStatus.DeletedFromIndex)] + [InlineData(true, "1/branch_file.txt", false, FileStatus.Unaltered, true, false, FileStatus.DeletedFromIndex)] /*** * Test case: file exists in the index, and has been removed from the wd. * 'git rm and 'git rm --cached ' both work (file removed from the index) */ - [InlineData(true, "deleted_unstaged_file.txt", false, FileStatus.Missing, false, false, FileStatus.Removed)] - [InlineData(false, "deleted_unstaged_file.txt", false, FileStatus.Missing, false, false, FileStatus.Removed)] + [InlineData(true, "deleted_unstaged_file.txt", false, FileStatus.DeletedFromWorkdir, false, false, FileStatus.DeletedFromIndex)] + [InlineData(false, "deleted_unstaged_file.txt", false, FileStatus.DeletedFromWorkdir, false, false, FileStatus.DeletedFromIndex)] /*** * Test case: modified file in wd, the modifications have not been promoted to the index yet. * 'git rm --cached ' works (removes the file from the index) * 'git rm ' fails ("error: '' has local modifications"). */ - [InlineData(false, "modified_unstaged_file.txt", false, FileStatus.Modified, true, true, FileStatus.Untracked | FileStatus.Removed)] - [InlineData(true, "modified_unstaged_file.txt", true, FileStatus.Modified, true, true, 0)] + [InlineData(false, "modified_unstaged_file.txt", false, FileStatus.ModifiedInWorkdir, true, true, FileStatus.NewInWorkdir | FileStatus.DeletedFromIndex)] + [InlineData(true, "modified_unstaged_file.txt", true, FileStatus.ModifiedInWorkdir, true, true, FileStatus.Unaltered)] /*** * Test case: modified file in wd, the modifications have already been promoted to the index. * 'git rm --cached ' works (removes the file from the index) * 'git rm ' fails ("error: '' has changes staged in the index") */ - [InlineData(false, "modified_staged_file.txt", false, FileStatus.Staged, true, true, FileStatus.Untracked | FileStatus.Removed)] - [InlineData(true, "modified_staged_file.txt", true, FileStatus.Staged, true, true, 0)] + [InlineData(false, "modified_staged_file.txt", false, FileStatus.ModifiedInIndex, true, true, FileStatus.NewInWorkdir | FileStatus.DeletedFromIndex)] + [InlineData(true, "modified_staged_file.txt", true, FileStatus.ModifiedInIndex, true, true, FileStatus.Unaltered)] /*** * Test case: modified file in wd, the modifications have already been promoted to the index, and * the file does not exist in the HEAD. * 'git rm --cached ' works (removes the file from the index) * 'git rm ' throws ("error: '' has changes staged in the index") */ - [InlineData(false, "new_tracked_file.txt", false, FileStatus.Added, true, true, FileStatus.Untracked)] - [InlineData(true, "new_tracked_file.txt", true, FileStatus.Added, true, true, 0)] + [InlineData(false, "new_tracked_file.txt", false, FileStatus.NewInIndex, true, true, FileStatus.NewInWorkdir)] + [InlineData(true, "new_tracked_file.txt", true, FileStatus.NewInIndex, true, true, FileStatus.Unaltered)] public void CanRemoveAnUnalteredFileFromTheIndexWithoutRemovingItFromTheWorkingDirectory( bool removeFromWorkdir, string filename, bool throws, FileStatus initialStatus, bool existsBeforeRemove, bool existsAfterRemove, FileStatus lastStatus) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.Equal(initialStatus, repo.Index.RetrieveStatus(filename)); + Assert.Equal(initialStatus, repo.RetrieveStatus(filename)); Assert.Equal(existsBeforeRemove, File.Exists(fullpath)); if (throws) { - Assert.Throws(() => repo.Index.Remove(filename, removeFromWorkdir)); + Assert.Throws(() => Commands.Remove(repo, filename, removeFromWorkdir)); Assert.Equal(count, repo.Index.Count); } else { - repo.Index.Remove(filename, removeFromWorkdir); + Commands.Remove(repo, filename, removeFromWorkdir); Assert.Equal(count - 1, repo.Index.Count); Assert.Equal(existsAfterRemove, File.Exists(fullpath)); - Assert.Equal(lastStatus, repo.Index.RetrieveStatus(filename)); + Assert.Equal(lastStatus, repo.RetrieveStatus(filename)); } } } @@ -83,37 +83,37 @@ public void RemovingAModifiedFileWhoseChangesHaveBeenPromotedToTheIndexAndWithAd { const string filename = "modified_staged_file.txt"; - var path = CloneStandardTestRepo(); + var path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.Equal(true, File.Exists(fullpath)); + Assert.True(File.Exists(fullpath)); File.AppendAllText(fullpath, "additional content"); - Assert.Equal(FileStatus.Staged | FileStatus.Modified, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(filename)); - Assert.Throws(() => repo.Index.Remove(filename)); - Assert.Throws(() => repo.Index.Remove(filename, false)); + Assert.Throws(() => Commands.Remove(repo, filename)); + Assert.Throws(() => Commands.Remove(repo, filename, false)); } } [Fact] public void CanRemoveAFolderThroughUsageOfPathspecsForNewlyAddedFiles() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Index.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir1/2.txt", "whone")); - repo.Index.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir1/3.txt", "too")); - repo.Index.Stage(Touch(repo.Info.WorkingDirectory, "2/subdir2/4.txt", "tree")); - repo.Index.Stage(Touch(repo.Info.WorkingDirectory, "2/5.txt", "for")); - repo.Index.Stage(Touch(repo.Info.WorkingDirectory, "2/6.txt", "fyve")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir1/2.txt", "whone")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir1/3.txt", "too")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/subdir2/4.txt", "tree")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/5.txt", "for")); + Commands.Stage(repo, Touch(repo.Info.WorkingDirectory, "2/6.txt", "fyve")); int count = repo.Index.Count; Assert.True(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "2"))); - repo.Index.Remove("2", false); + Commands.Remove(repo, "2", false); Assert.Equal(count - 5, repo.Index.Count); } @@ -122,13 +122,13 @@ public void CanRemoveAFolderThroughUsageOfPathspecsForNewlyAddedFiles() [Fact] public void CanRemoveAFolderThroughUsageOfPathspecsForFilesAlreadyInTheIndexAndInTheHEAD() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; Assert.True(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); - repo.Index.Remove("1"); + Commands.Remove(repo, "1"); Assert.False(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); Assert.Equal(count - 1, repo.Index.Count); @@ -136,38 +136,40 @@ public void CanRemoveAFolderThroughUsageOfPathspecsForFilesAlreadyInTheIndexAndI } [Theory] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] public void RemovingAnUnknownFileWithLaxExplicitPathsValidationDoesntThrow(string relativePath, FileStatus status) { for (int i = 0; i < 2; i++) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); - repo.Index.Remove(relativePath, i % 2 == 0); - repo.Index.Remove(relativePath, i % 2 == 0, - new ExplicitPathsOptions {ShouldFailOnUnmatchedPath = false}); + Commands.Remove(repo, relativePath, i % 2 == 0); + Commands.Remove(repo, relativePath, i % 2 == 0, + new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); } } } [Theory] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] public void RemovingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileStatus status) { for (int i = 0; i < 2; i++) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); Assert.Throws( - () => repo.Index.Remove(relativePath, i%2 == 0, new ExplicitPathsOptions())); + () => Commands.Remove(repo, relativePath, i % 2 == 0, new ExplicitPathsOptions())); } } } @@ -175,12 +177,13 @@ public void RemovingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileS [Fact] public void RemovingFileWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Index.Remove(string.Empty)); - Assert.Throws(() => repo.Index.Remove((string)null)); - Assert.Throws(() => repo.Index.Remove(new string[] { })); - Assert.Throws(() => repo.Index.Remove(new string[] { null })); + Assert.Throws(() => Commands.Remove(repo, string.Empty)); + Assert.Throws(() => Commands.Remove(repo, (string)null)); + Assert.Throws(() => Commands.Remove(repo, Array.Empty())); + Assert.Throws(() => Commands.Remove(repo, new string[] { null })); } } } diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index 62ca9e1b5..ef3e72f07 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -26,6 +26,7 @@ public void CanCreateBareRepo() Assert.Null(repo.Info.WorkingDirectory); Assert.Equal(Path.GetFullPath(repoPath), repo.Info.Path); Assert.True(repo.Info.IsBare); + Assert.Throws(() => { var idx = repo.Index; }); AssertInitializedRepository(repo, "refs/heads/master"); @@ -37,7 +38,8 @@ public void CanCreateBareRepo() [Fact] public void AccessingTheIndexInABareRepoThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Index); } @@ -55,6 +57,26 @@ public void CanCheckIfADirectoryLeadsToAValidRepository() Assert.False(Repository.IsValid(scd.DirectoryPath)); } + + [Fact] + public void IsValidWithNullPathThrows() + { + Assert.Throws(() => Repository.IsValid(null)); + } + + [Fact] + public void IsNotValidWithEmptyPath() + { + Assert.False(Repository.IsValid(string.Empty)); + } + + [Fact] + public void IsValidWithValidPath() + { + string repoPath = InitNewRepository(); + Assert.True(Repository.IsValid(repoPath)); + } + [Fact] public void CanCreateStandardRepo() { @@ -100,7 +122,7 @@ public void CanCreateStandardRepoAndSpecifyAFolderWhichWillContainTheNewlyCreate Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); Assert.True(Repository.IsValid(repo.Info.Path)); - Assert.Equal(false, repo.Info.IsBare); + Assert.False(repo.Info.IsBare); char sep = Path.DirectorySeparatorChar; Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); @@ -125,7 +147,7 @@ public void CanCreateStandardRepoAndDirectlySpecifyAGitDirectory() Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); Assert.True(Repository.IsValid(repo.Info.Path)); - Assert.Equal(false, repo.Info.IsBare); + Assert.False(repo.Info.IsBare); char sep = Path.DirectorySeparatorChar; Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); @@ -144,12 +166,16 @@ private static void CheckGitConfigFile(string dir) private static void AssertIsHidden(string repoPath) { + //Workaround for .NET Core 1.x never considering a directory hidden if the path has a trailing slash + //https://github.com/dotnet/corefx/issues/18520 + repoPath = repoPath.TrimEnd('/'); + FileAttributes attribs = File.GetAttributes(repoPath); Assert.Equal(FileAttributes.Hidden, (attribs & FileAttributes.Hidden)); } - [Fact(Skip = "Skipping due to recent github handling modification of --include-tag.")] + [Fact] public void CanFetchFromRemoteByName() { string remoteName = "testRemote"; @@ -159,7 +185,7 @@ public void CanFetchFromRemoteByName() using (var repo = new Repository(repoPath)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // We will first fetch without specifying any Tag options. // After we verify this fetch, we will perform a second fetch @@ -186,13 +212,13 @@ public void CanFetchFromRemoteByName() } // Perform the actual fetch - repo.Fetch(remote.Name, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }); + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }, null); // Verify the expected state expectedFetchState.CheckUpdatedReferences(repo); // Now fetch the rest of the tags - repo.Fetch(remote.Name, new FetchOptions { TagFetchMode = TagFetchMode.All }); + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions { TagFetchMode = TagFetchMode.All }, null); // Verify that the "nearly-dangling" tag is now in the repo. Tag nearlyDanglingTag = repo.Tags["nearly-dangling"]; @@ -240,24 +266,25 @@ private static void AssertInitializedRepository(IRepository repo, string expecte Assert.Equal(headRef.TargetIdentifier, repo.Head.CanonicalName); Assert.Null(repo.Head.Tip); - Assert.Equal(0, repo.Commits.Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter()).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = repo.Refs.Head }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = repo.Head }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = "HEAD" }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new CommitFilter { Since = expectedHeadTargetIdentifier }).Count()); + Assert.Empty(repo.Commits); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter())); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs.Head })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Head })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "HEAD" })); + Assert.Empty(repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = expectedHeadTargetIdentifier })); Assert.Null(repo.Head["subdir/I-do-not-exist"]); - Assert.Equal(0, repo.Branches.Count()); - Assert.Equal(0, repo.Refs.Count()); - Assert.Equal(0, repo.Tags.Count()); + Assert.Empty(repo.Branches); + Assert.Empty(repo.Refs); + Assert.Empty(repo.Tags); } [Fact] public void CanOpenBareRepositoryThroughAFullPathToTheGitDir() { - string path = Path.GetFullPath(BareTestRepoPath); + string relPath = SandboxBareTestRepo(); + string path = Path.GetFullPath(relPath); using (var repo = new Repository(path)) { Assert.NotNull(repo); @@ -268,7 +295,8 @@ public void CanOpenBareRepositoryThroughAFullPathToTheGitDir() [Fact] public void CanOpenStandardRepositoryThroughAWorkingDirPath() { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.NotNull(repo); Assert.NotNull(repo.Info.WorkingDirectory); @@ -278,7 +306,8 @@ public void CanOpenStandardRepositoryThroughAWorkingDirPath() [Fact] public void OpeningStandardRepositoryThroughTheGitDirGuessesTheWorkingDirPath() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.NotNull(repo); Assert.NotNull(repo.Info.WorkingDirectory); @@ -288,7 +317,8 @@ public void OpeningStandardRepositoryThroughTheGitDirGuessesTheWorkingDirPath() [Fact] public void CanOpenRepository() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.NotNull(repo.Info.Path); Assert.Null(repo.Info.WorkingDirectory); @@ -313,7 +343,8 @@ public void OpeningRepositoryWithBadParamsThrows() [Fact] public void CanLookupACommitByTheNameOfABranch() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject gitObject = repo.Lookup("refs/heads/master"); Assert.NotNull(gitObject); @@ -324,7 +355,8 @@ public void CanLookupACommitByTheNameOfABranch() [Fact] public void CanLookupACommitByTheNameOfALightweightTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject gitObject = repo.Lookup("refs/tags/lw"); Assert.NotNull(gitObject); @@ -335,7 +367,8 @@ public void CanLookupACommitByTheNameOfALightweightTag() [Fact] public void CanLookupATagAnnotationByTheNameOfAnAnnotatedTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject gitObject = repo.Lookup("refs/tags/e90810b"); Assert.NotNull(gitObject); @@ -346,7 +379,8 @@ public void CanLookupATagAnnotationByTheNameOfAnAnnotatedTag() [Fact] public void CanLookupObjects() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.NotNull(repo.Lookup(commitSha)); Assert.NotNull(repo.Lookup(commitSha)); @@ -357,7 +391,8 @@ public void CanLookupObjects() [Fact] public void CanLookupSameObjectTwiceAndTheyAreEqual() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject commit = repo.Lookup(commitSha); GitObject commit2 = repo.Lookup(commitSha); @@ -369,7 +404,8 @@ public void CanLookupSameObjectTwiceAndTheyAreEqual() [Fact] public void LookupObjectByWrongShaReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Null(repo.Lookup(Constants.UnknownSha)); Assert.Null(repo.Lookup(Constants.UnknownSha)); @@ -379,7 +415,8 @@ public void LookupObjectByWrongShaReturnsNull() [Fact] public void LookupObjectByWrongTypeReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.NotNull(repo.Lookup(commitSha)); Assert.NotNull(repo.Lookup(commitSha)); @@ -390,7 +427,8 @@ public void LookupObjectByWrongTypeReturnsNull() [Fact] public void LookupObjectByUnknownReferenceNameReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Null(repo.Lookup("refs/heads/chopped/off")); Assert.Null(repo.Lookup(Constants.UnknownSha)); @@ -409,7 +447,7 @@ public void CanLookupWhithShortIdentifers() { const string filename = "new.txt"; Touch(repo.Info.WorkingDirectory, filename, "one "); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); Signature author = Constants.Signature; Commit commit = repo.Commit("Initial commit", author, author); @@ -427,7 +465,8 @@ public void CanLookupWhithShortIdentifers() [Fact] public void CanLookupUsingRevparseSyntax() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Null(repo.Lookup("master^")); @@ -444,7 +483,8 @@ public void CanLookupUsingRevparseSyntax() [Fact] public void CanResolveAmbiguousRevparseSpecs() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var o1 = repo.Lookup("e90810b"); // This resolves to a tag Assert.Equal("7b4384978d2493e851f9cca7858815fac9b10980", o1.Sha); @@ -456,7 +496,8 @@ public void CanResolveAmbiguousRevparseSpecs() [Fact] public void LookingUpWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Lookup(string.Empty)); Assert.Throws(() => repo.Lookup(string.Empty)); @@ -470,16 +511,33 @@ public void LookingUpWithBadParamsThrows() [Fact] public void LookingUpWithATooShortShaThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Lookup("e90")); } } + [Fact] + public void LookingUpByAWrongRevParseExpressionThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Lookup("tags/point_to_blob^{tree}")); + Assert.Throws(() => repo.Lookup("tags/point_to_blob^{commit}")); + Assert.Throws(() => repo.Lookup("tags/point_to_blob^{commit}")); + Assert.Throws(() => repo.Lookup("master^{tree}^{blob}")); + Assert.Throws(() => repo.Lookup("master^{blob}")); + Assert.Throws(() => repo.Lookup("tags/e90810b^{blob}")); + } + } + [Fact] public void LookingUpAGitLinkThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Lookup("e90810b")); } @@ -516,27 +574,26 @@ public void CanDiscoverAStandardRepoGivenASubDirectoryOfTheRepoPath() [Fact] public void CanDiscoverAStandardRepoGivenTheWorkingDirPath() { - string path = Repository.Discover(StandardTestRepoWorkingDirPath); - Assert.Equal(Path.GetFullPath(StandardTestRepoPath + Path.DirectorySeparatorChar), path); + string path = Sandbox(StandardTestRepoWorkingDirPath); + + string found = Repository.Discover(path); + Assert.Equal(Path.GetFullPath(string.Format("{0}{1}.git{1}", path, Path.DirectorySeparatorChar)), found); } [Fact] public void DiscoverReturnsNullWhenNoRepoCanBeFound() { - string path = Path.GetTempFileName(); - string suffix = "." + Guid.NewGuid().ToString().Substring(0, 7); + string path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(path + suffix); + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(path); Directory.CreateDirectory(scd.RootedDirectoryPath); Assert.Null(Repository.Discover(scd.RootedDirectoryPath)); - - File.Delete(path); } [Fact] public void CanDetectIfTheHeadIsOrphaned() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { string branchName = repo.Head.CanonicalName; @@ -554,12 +611,12 @@ public void CanDetectIfTheHeadIsOrphaned() [Fact] public void QueryingTheRemoteForADetachedHeadBranchReturnsNull() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); + Commands.Checkout(repo, repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); Branch trackLocal = repo.Head; - Assert.Null(trackLocal.Remote); + Assert.Null(trackLocal.RemoteName); } } @@ -607,15 +664,116 @@ public void AccessingADeletedHeadThrows() [Fact] public void CanDetectShallowness() { - using (var repo = new Repository(ShallowTestRepoPath)) + var path = Sandbox(ShallowTestRepoPath); + using (var repo = new Repository(path)) { Assert.True(repo.Info.IsShallow); } - using (var repo = new Repository(StandardTestRepoPath)) + path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.False(repo.Info.IsShallow); } } + + [Fact] + public void CanCreateInMemoryRepository() + { + using (var repo = new Repository()) + { + Assert.True(repo.Info.IsBare); + Assert.Null(repo.Info.Path); + Assert.Null(repo.Info.WorkingDirectory); + + Assert.Throws(() => { var idx = repo.Index; }); + } + } + + [SkippableFact] + public void CanListRemoteReferencesWithCredentials() + { + InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), + "Populate Constants.PrivateRepo* to run this test"); + + IEnumerable references = Repository.ListRemoteReferences(Constants.PrivateRepoUrl, + Constants.PrivateRepoCredentials); + + foreach (var reference in references) + { + Assert.NotNull(reference); + } + } + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferences(string url) + { + IEnumerable references = Repository.ListRemoteReferences(url).ToList(); + + List> actualRefs = references. + Select(reference => new Tuple(reference.CanonicalName, reference.ResolveToDirectReference().TargetIdentifier)).ToList(); + + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs.Count, actualRefs.Count); + Assert.True(references.Single(reference => reference.CanonicalName == "HEAD") is SymbolicReference); + for (int i = 0; i < TestRemoteRefs.ExpectedRemoteRefs.Count; i++) + { + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); + } + } + + [Fact] + public void CanListRemoteReferencesWithDetachedRemoteHead() + { + string originalRepoPath = SandboxStandardTestRepo(); + + string detachedHeadSha; + + using (var originalRepo = new Repository(originalRepoPath)) + { + detachedHeadSha = originalRepo.Head.Tip.Sha; + Commands.Checkout(originalRepo, detachedHeadSha); + + Assert.True(originalRepo.Info.IsHeadDetached); + } + + IEnumerable references = Repository.ListRemoteReferences(originalRepoPath); + + Reference head = references.SingleOrDefault(reference => reference.CanonicalName == "HEAD"); + + Assert.NotNull(head); + Assert.True(head is DirectReference); + Assert.Equal(detachedHeadSha, head.TargetIdentifier); + } + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + public void ReadingReferenceRepositoryThroughListRemoteReferencesThrows(string url) + { + IEnumerable references = Repository.ListRemoteReferences(url); + + foreach (var reference in references) + { + IBelongToARepository repositoryReference = reference; + Assert.Throws(() => repositoryReference.Repository); + } + } + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + public void ReadingReferenceTargetFromListRemoteReferencesThrows(string url) + { + IEnumerable references = Repository.ListRemoteReferences(url); + + foreach (var reference in references) + { + Assert.Throws(() => + { + var target = reference.ResolveToDirectReference().Target; + }); + } + } } } diff --git a/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs b/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs index 946d4a31a..46863f44d 100644 --- a/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs @@ -31,10 +31,11 @@ public void CanOpenABareRepoAsIfItWasAStandardOne() var options = new RepositoryOptions { WorkingDirectoryPath = newWorkdir, IndexPath = newIndex }; - using (var repo = new Repository(BareTestRepoPath, options)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, options)) { - var st = repo.Index.RetrieveStatus("1/branch_file.txt"); - Assert.Equal(FileStatus.Missing, st); + var st = repo.RetrieveStatus("1/branch_file.txt"); + Assert.Equal(FileStatus.DeletedFromWorkdir, st); } } @@ -43,71 +44,85 @@ public void CanOpenABareRepoAsIfItWasAStandardOneWithANonExisitingIndexFile() { var options = new RepositoryOptions { WorkingDirectoryPath = newWorkdir, IndexPath = newIndex }; - using (var repo = new Repository(BareTestRepoPath, options)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, options)) { - var st = repo.Index.RetrieveStatus("1/branch_file.txt"); - Assert.Equal(FileStatus.Removed, st); + var st = repo.RetrieveStatus("1/branch_file.txt"); + Assert.Equal(FileStatus.DeletedFromIndex, st); + } + } + + [Fact] + public void CanOpenABareRepoWithOptions() + { + var options = new RepositoryOptions { }; + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, options)) + { + Assert.True(repo.Info.IsBare); } } [Fact] public void CanProvideADifferentWorkDirToAStandardRepo() { - var path1 = CloneStandardTestRepo(); + var path1 = SandboxStandardTestRepo(); using (var repo = new Repository(path1)) { - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus("1/branch_file.txt")); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus("1/branch_file.txt")); } var options = new RepositoryOptions { WorkingDirectoryPath = newWorkdir }; - var path2 = CloneStandardTestRepo(); + var path2 = SandboxStandardTestRepo(); using (var repo = new Repository(path2, options)) { - Assert.Equal(FileStatus.Missing, repo.Index.RetrieveStatus("1/branch_file.txt")); + Assert.Equal(FileStatus.DeletedFromWorkdir, repo.RetrieveStatus("1/branch_file.txt")); } } [Fact] public void CanProvideADifferentIndexToAStandardRepo() { - var path1 = CloneStandardTestRepo(); + var path1 = SandboxStandardTestRepo(); using (var repo = new Repository(path1)) { - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus("new_untracked_file.txt")); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("new_untracked_file.txt")); - repo.Index.Stage("new_untracked_file.txt"); + Commands.Stage(repo, "new_untracked_file.txt"); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus("new_untracked_file.txt")); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus("new_untracked_file.txt")); File.Copy(Path.Combine(repo.Info.Path, "index"), newIndex); } var options = new RepositoryOptions { IndexPath = newIndex }; - var path2 = CloneStandardTestRepo(); + var path2 = SandboxStandardTestRepo(); using (var repo = new Repository(path2, options)) { - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus("new_untracked_file.txt")); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus("new_untracked_file.txt")); } } [Fact] public void OpeningABareRepoWithoutProvidingBothWorkDirAndIndexThrows() { - Assert.Throws(() => new Repository(BareTestRepoPath, new RepositoryOptions {IndexPath = newIndex})); - Assert.Throws(() => new Repository(BareTestRepoPath, new RepositoryOptions {WorkingDirectoryPath = newWorkdir})); + string path = SandboxBareTestRepo(); + Assert.Throws(() => new Repository(path, new RepositoryOptions { IndexPath = newIndex })); + Assert.Throws(() => new Repository(path, new RepositoryOptions { WorkingDirectoryPath = newWorkdir })); } [Fact] public void CanSneakAdditionalCommitsIntoAStandardRepoWithoutAlteringTheWorkdirOrTheIndex() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Branch head = repo.Head; - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("zomg.txt")); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus("zomg.txt")); string commitSha = MeanwhileInAnotherDimensionAnEvilMastermindIsAtWork(path); @@ -116,7 +131,7 @@ public void CanSneakAdditionalCommitsIntoAStandardRepoWithoutAlteringTheWorkdirO Assert.NotEqual(head.Tip.Sha, newHead.Tip.Sha); Assert.Equal(commitSha, newHead.Tip.Sha); - Assert.Equal(FileStatus.Removed, repo.Index.RetrieveStatus("zomg.txt")); + Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus("zomg.txt")); } } @@ -133,49 +148,11 @@ private string MeanwhileInAnotherDimensionAnEvilMastermindIsAtWork(string workin const string filename = "zomg.txt"; Touch(sneakyRepo.Info.WorkingDirectory, filename, "I'm being sneaked in!\n"); - sneakyRepo.Index.Stage(filename); + Commands.Stage(sneakyRepo, filename); return sneakyRepo.Commit("Tadaaaa!", Constants.Signature, Constants.Signature).Sha; } } - [Fact] - public void CanProvideDifferentConfigurationFilesToARepository() - { - string globalLocation = Path.Combine(newWorkdir, "my-global-config"); - string xdgLocation = Path.Combine(newWorkdir, "my-xdg-config"); - string systemLocation = Path.Combine(newWorkdir, "my-system-config"); - - const string name = "Adam 'aroben' Roben"; - const string email = "adam@github.com"; - - StringBuilder sb = new StringBuilder() - .AppendLine("[user]") - .AppendFormat("name = {0}{1}", name, Environment.NewLine) - .AppendFormat("email = {0}{1}", email, Environment.NewLine); - - File.WriteAllText(globalLocation, sb.ToString()); - File.WriteAllText(systemLocation, string.Empty); - File.WriteAllText(xdgLocation, string.Empty); - - var options = new RepositoryOptions { - GlobalConfigurationLocation = globalLocation, - XdgConfigurationLocation = xdgLocation, - SystemConfigurationLocation = systemLocation, - }; - - using (var repo = new Repository(BareTestRepoPath, options)) - { - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); - Assert.Equal(name, repo.Config.Get("user.name").Value); - Assert.Equal(email, repo.Config.Get("user.email").Value); - - repo.Config.Set("xdg.setting", "https://twitter.com/libgit2sharp", ConfigurationLevel.Xdg); - repo.Config.Set("help.link", "https://twitter.com/xpaulbettsx/status/205761932626636800", ConfigurationLevel.System); - } - - AssertValueInConfigFile(systemLocation, "xpaulbettsx"); - } - [Fact] public void CanCommitOnBareRepository() { @@ -194,11 +171,11 @@ public void CanCommitOnBareRepository() { const string relativeFilepath = "test.txt"; Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.NotNull(repo.Commit("Initial commit", Constants.Signature, Constants.Signature)); - Assert.Equal(1, repo.Head.Commits.Count()); - Assert.Equal(1, repo.Commits.Count()); + Assert.Single(repo.Head.Commits); + Assert.Single(repo.Commits); } } } diff --git a/LibGit2Sharp.Tests/ResetHeadFixture.cs b/LibGit2Sharp.Tests/ResetHeadFixture.cs index 870e46c39..5fb841ae0 100644 --- a/LibGit2Sharp.Tests/ResetHeadFixture.cs +++ b/LibGit2Sharp.Tests/ResetHeadFixture.cs @@ -25,7 +25,7 @@ public void ResetANewlyInitializedRepositoryThrows(bool isBare) [Fact] public void SoftResetToTheHeadOfARepositoryDoesNotChangeTheTargetOfTheHead() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Branch oldHead = repo.Head; @@ -39,7 +39,7 @@ public void SoftResetToTheHeadOfARepositoryDoesNotChangeTheTargetOfTheHead() [Fact] public void SoftResetToAParentCommitChangesTheTargetOfTheHead() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var headCommit = repo.Head.Tip; @@ -53,7 +53,7 @@ public void SoftResetToAParentCommitChangesTheTargetOfTheHead() [Fact] public void SoftResetSetsTheHeadToTheDereferencedCommitOfAChainedTag() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag tag = repo.Tags["test"]; @@ -65,13 +65,14 @@ public void SoftResetSetsTheHeadToTheDereferencedCommitOfAChainedTag() [Fact] public void ResettingWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Reset(ResetMode.Soft, (string)null)); Assert.Throws(() => repo.Reset(ResetMode.Soft, (Commit)null)); Assert.Throws(() => repo.Reset(ResetMode.Soft, "")); - Assert.Throws(() => repo.Reset(ResetMode.Soft, Constants.UnknownSha)); - Assert.Throws(() => repo.Reset(ResetMode.Soft, repo.Head.Tip.Tree.Sha)); + Assert.Throws(() => repo.Reset(ResetMode.Soft, Constants.UnknownSha)); + Assert.Throws(() => repo.Reset(ResetMode.Soft, repo.Head.Tip.Tree.Sha)); } } @@ -79,7 +80,7 @@ public void ResettingWithBadParamsThrows() public void SoftResetSetsTheHeadToTheSpecifiedCommit() { /* Make the Head point to a branch through its name */ - AssertSoftReset(b => b.Name, false, b => b.Name); + AssertSoftReset(b => b.FriendlyName, false, b => b.FriendlyName); } [Fact] @@ -93,7 +94,7 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo { string repoPath = InitNewRepository(); - using (var repo = new Repository(repoPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) { FeedTheRepository(repo); @@ -101,54 +102,60 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo Branch branch = repo.Branches["mybranch"]; string branchIdentifier = branchIdentifierRetriever(branch); - repo.Checkout(branchIdentifier); + Commands.Checkout(repo, branchIdentifier); var oldHeadId = repo.Head.Tip.Id; Assert.Equal(shouldHeadBeDetached, repo.Info.IsHeadDetached); string expectedHeadName = expectedHeadNameRetriever(branch); - Assert.Equal(expectedHeadName, repo.Head.Name); + Assert.Equal(expectedHeadName, repo.Head.FriendlyName); Assert.Equal(branch.Tip.Sha, repo.Head.Tip.Sha); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + /* Reset --soft the Head to a tag through its canonical name */ repo.Reset(ResetMode.Soft, tag.CanonicalName); - Assert.Equal(expectedHeadName, repo.Head.Name); + Assert.Equal(expectedHeadName, repo.Head.FriendlyName); Assert.Equal(tag.Target.Id, repo.Head.Tip.Id); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus("a.txt")); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("a.txt")); AssertRefLogEntry(repo, "HEAD", - tag.Target.Id, string.Format("reset: moving to {0}", tag.Target.Sha), - oldHeadId); + oldHeadId, + tag.Target.Id, + Constants.Identity, before); if (!shouldHeadBeDetached) { AssertRefLogEntry(repo, branch.CanonicalName, - tag.Target.Id, string.Format("reset: moving to {0}", tag.Target.Sha), - oldHeadId); + oldHeadId, + tag.Target.Id, + Constants.Identity, before); } + before = DateTimeOffset.Now.TruncateMilliseconds(); + /* Reset --soft the Head to a commit through its sha */ - repo.Reset(ResetMode.Soft, branch.Tip.Sha, Constants.Signature, "FOO"); - Assert.Equal(expectedHeadName, repo.Head.Name); + repo.Reset(ResetMode.Soft, branch.Tip.Sha); + Assert.Equal(expectedHeadName, repo.Head.FriendlyName); Assert.Equal(branch.Tip.Sha, repo.Head.Tip.Sha); - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus("a.txt")); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus("a.txt")); AssertRefLogEntry(repo, "HEAD", - branch.Tip.Id, - "FOO", + string.Format("reset: moving to {0}", branch.Tip.Sha), tag.Target.Id, - Constants.Signature); + branch.Tip.Id, + Constants.Identity, before); if (!shouldHeadBeDetached) { AssertRefLogEntry(repo, branch.CanonicalName, - branch.Tip.Id, - "FOO", + string.Format("reset: moving to {0}", branch.Tip.Sha), tag.Target.Id, - Constants.Signature); + branch.Tip.Id, + Constants.Identity, before); } } } @@ -156,20 +163,20 @@ private void AssertSoftReset(Func branchIdentifierRetriever, boo private static void FeedTheRepository(IRepository repo) { string fullPath = Touch(repo.Info.WorkingDirectory, "a.txt", "Hello\n"); - repo.Index.Stage(fullPath); + Commands.Stage(repo, fullPath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); repo.ApplyTag("mytag"); File.AppendAllText(fullPath, "World\n"); - repo.Index.Stage(fullPath); + Commands.Stage(repo, fullPath); Signature shiftedSignature = Constants.Signature.TimeShift(TimeSpan.FromMinutes(1)); repo.Commit("Update file", shiftedSignature, shiftedSignature); repo.CreateBranch("mybranch"); - repo.Checkout("mybranch"); + Commands.Checkout(repo, "mybranch"); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); } [Fact] @@ -177,7 +184,7 @@ public void MixedResetRefreshesTheIndex() { string repoPath = InitNewRepository(); - using (var repo = new Repository(repoPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) { FeedTheRepository(repo); @@ -185,26 +192,31 @@ public void MixedResetRefreshesTheIndex() Tag tag = repo.Tags["mytag"]; + var before = DateTimeOffset.Now.TruncateMilliseconds(); + repo.Reset(ResetMode.Mixed, tag.CanonicalName); - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus("a.txt")); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus("a.txt")); AssertRefLogEntry(repo, "HEAD", - tag.Target.Id, string.Format("reset: moving to {0}", tag.Target.Sha), - oldHeadId); + oldHeadId, + tag.Target.Id, + Constants.Identity, before); AssertRefLogEntry(repo, "refs/heads/mybranch", - tag.Target.Id, string.Format("reset: moving to {0}", tag.Target.Sha), - oldHeadId); + oldHeadId, + tag.Target.Id, + Constants.Identity, before); } } [Fact] public void MixedResetInABareRepositoryThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Reset(ResetMode.Mixed)); } @@ -213,7 +225,8 @@ public void MixedResetInABareRepositoryThrows() [Fact] public void HardResetInABareRepositoryThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Reset(ResetMode.Hard)); } @@ -222,7 +235,9 @@ public void HardResetInABareRepositoryThrows() [Fact] public void HardResetUpdatesTheContentOfTheWorkingDirectory() { - string path = CloneStandardTestRepo(); + bool progressCalled = false; + + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var names = new DirectoryInfo(repo.Info.WorkingDirectory).GetFileSystemInfos().Select(fsi => fsi.Name).ToList(); @@ -232,11 +247,16 @@ public void HardResetUpdatesTheContentOfTheWorkingDirectory() Assert.True(names.Count > 4); - repo.Reset(ResetMode.Hard, "HEAD~3"); + var commit = repo.Lookup("HEAD~3"); + repo.Reset(ResetMode.Hard, commit, new CheckoutOptions() + { + OnCheckoutProgress = (_path, _completed, _total) => { progressCalled = true; }, + }); names = new DirectoryInfo(repo.Info.WorkingDirectory).GetFileSystemInfos().Select(fsi => fsi.Name).ToList(); names.Sort(StringComparer.Ordinal); + Assert.True(progressCalled); Assert.Equal(new[] { ".git", "README", "WillNotBeRemoved.txt", "branch_file.txt", "new.txt", "new_untracked_file.txt" }, names); } } diff --git a/LibGit2Sharp.Tests/ResetIndexFixture.cs b/LibGit2Sharp.Tests/ResetIndexFixture.cs index 9cc7849ac..d0228ae2b 100644 --- a/LibGit2Sharp.Tests/ResetIndexFixture.cs +++ b/LibGit2Sharp.Tests/ResetIndexFixture.cs @@ -1,4 +1,3 @@ -using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -14,43 +13,33 @@ public void ResetANewlyInitializedBareRepositoryThrows() using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.Reset()); - } - } - - [Fact] - public void ResetANewlyInitializedNonBareRepositoryThrows() - { - string repoPath = InitNewRepository(false); - - using (var repo = new Repository(repoPath)) - { - Assert.Throws(() => repo.Reset()); + Assert.Throws(() => repo.Index.Replace(repo.Head.Tip)); } } [Fact] public void ResettingInABareRepositoryThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Reset()); + Assert.Throws(() => repo.Index.Replace(repo.Head.Tip)); } } private static bool IsStaged(StatusEntry entry) { - if ((entry.State & FileStatus.Added) == FileStatus.Added) + if ((entry.State & FileStatus.NewInIndex) == FileStatus.NewInIndex) { return true; } - if ((entry.State & FileStatus.Staged) == FileStatus.Staged) + if ((entry.State & FileStatus.ModifiedInIndex) == FileStatus.ModifiedInIndex) { return true; } - if ((entry.State & FileStatus.Removed) == FileStatus.Removed) + if ((entry.State & FileStatus.DeletedFromIndex) == FileStatus.DeletedFromIndex) { return true; } @@ -61,53 +50,35 @@ private static bool IsStaged(StatusEntry entry) [Fact] public void ResetTheIndexWithTheHeadUnstagesEverything() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - RepositoryStatus oldStatus = repo.Index.RetrieveStatus(); + RepositoryStatus oldStatus = repo.RetrieveStatus(); Assert.Equal(3, oldStatus.Where(IsStaged).Count()); var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); - repo.Reset(); + repo.Index.Replace(repo.Head.Tip); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.Where(IsStaged).Count()); + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.DoesNotContain(newStatus, IsStaged); // Assert that no reflog entry is created Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); } } - [Fact] - public void CanResetTheIndexToTheContentOfACommitWithCommittishAsArgument() - { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) - { - repo.Reset("be3563a"); - - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - - var expected = new[] { "1.txt", Path.Combine("1", "branch_file.txt"), "deleted_staged_file.txt", - "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt" }; - - Assert.Equal(expected.Length, newStatus.Where(IsStaged).Count()); - Assert.Equal(expected, newStatus.Removed.Select(s => s.FilePath)); - } - } - [Fact] public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Reset(repo.Lookup("be3563a")); + repo.Index.Replace(repo.Lookup("be3563a")); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); + RepositoryStatus newStatus = repo.RetrieveStatus(); - var expected = new[] { "1.txt", Path.Combine("1", "branch_file.txt"), "deleted_staged_file.txt", + var expected = new[] { "1.txt", string.Join("/", "1", "branch_file.txt"), "deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt" }; Assert.Equal(expected.Length, newStatus.Where(IsStaged).Count()); @@ -115,26 +86,13 @@ public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument() } } - [Fact] - public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommittishAsArgument() - { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) - { - repo.Reset("5b5b025", new[]{ "new.txt" }); - - Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", repo.Index["README"].Id.Sha); - Assert.Equal("fa49b077972391ad58037050f2a75f74e3671e92", repo.Index["new.txt"].Id.Sha); - } - } - [Fact] public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgumentAndLaxUnmatchedExplicitPathsValidation() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Reset(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, + repo.Index.Replace(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", repo.Index["README"].Id.Sha); @@ -145,64 +103,64 @@ public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgumentAn [Fact] public void ResettingTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgumentAndStrictUnmatchedPathspecsValidationThrows() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { Assert.Throws(() => - repo.Reset(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, new ExplicitPathsOptions())); + repo.Index.Replace(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, new ExplicitPathsOptions())); } } [Fact] public void CanResetTheIndexWhenARenameExists() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); - repo.Reset(repo.Lookup("32eab9c")); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + repo.Index.Replace(repo.Lookup("32eab9c")); - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.Equal(0, status.Where(IsStaged).Count()); + RepositoryStatus status = repo.RetrieveStatus(); + Assert.DoesNotContain(status, IsStaged); } } [Fact] public void CanResetSourceOfARenameInIndex() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); - RepositoryStatus oldStatus = repo.Index.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.Nonexistent, oldStatus["branch_file.txt"].State); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Reset(repo.Lookup("32eab9c"), new string[] { "branch_file.txt" }); + repo.Index.Replace(repo.Lookup("32eab9c"), new string[] { "branch_file.txt" }); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); - Assert.Equal(FileStatus.Missing, newStatus["branch_file.txt"].State); - Assert.Equal(FileStatus.Added, newStatus["renamed_branch_file.txt"].State); + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.Empty(newStatus.RenamedInIndex); + Assert.Equal(FileStatus.DeletedFromWorkdir, newStatus["branch_file.txt"].State); + Assert.Equal(FileStatus.NewInIndex, newStatus["renamed_branch_file.txt"].State); } } [Fact] public void CanResetTargetOfARenameInIndex() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); - RepositoryStatus oldStatus = repo.Index.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Reset(repo.Lookup("32eab9c"), new string[] { "renamed_branch_file.txt" }); + repo.Index.Replace(repo.Lookup("32eab9c"), new string[] { "renamed_branch_file.txt" }); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); - Assert.Equal(FileStatus.Untracked, newStatus["renamed_branch_file.txt"].State); - Assert.Equal(FileStatus.Removed, newStatus["branch_file.txt"].State); + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.Empty(newStatus.RenamedInIndex); + Assert.Equal(FileStatus.NewInWorkdir, newStatus["renamed_branch_file.txt"].State); + Assert.Equal(FileStatus.DeletedFromIndex, newStatus["branch_file.txt"].State); } } } diff --git a/LibGit2Sharp.Tests/Resources/.gitattributes b/LibGit2Sharp.Tests/Resources/.gitattributes index 23d9bdb29..48eb3141c 100644 --- a/LibGit2Sharp.Tests/Resources/.gitattributes +++ b/LibGit2Sharp.Tests/Resources/.gitattributes @@ -1 +1,4 @@ * binary +.gitattributes diff +.gitignore diff +config diff diff --git a/LibGit2Sharp.Tests/Resources/.gitignore b/LibGit2Sharp.Tests/Resources/.gitignore index 29ebe04b9..6b70b2273 100644 --- a/LibGit2Sharp.Tests/Resources/.gitignore +++ b/LibGit2Sharp.Tests/Resources/.gitignore @@ -1 +1,6 @@ *.sample +COMMIT_EDITMSG +exclude +logs/ +!testrepo_wd/dot_git/logs/ +description diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/COMMIT_EDITMSG new file mode 100644 index 000000000..5852f4463 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Initial commit diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/index new file mode 100644 index 000000000..1af67f2d4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/info/exclude similarity index 100% rename from LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/info/exclude rename to LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/info/exclude diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 new file mode 100644 index 000000000..3c800a797 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 @@ -0,0 +1,3 @@ +xI +B1]>'[ oU/Zni"5SQhC.n=? \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b new file mode 100644 index 000000000..783449ff9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b differ diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 new file mode 100644 index 000000000..6b011038d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 differ diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a new file mode 100644 index 000000000..6802d4949 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a differ diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..2ed6cd9fa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +872129051d644790636b416d1ef1ec830c5f6b90 diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/hello.txt b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/hello.txt new file mode 100644 index 000000000..e965047ad --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/world.txt b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/world.txt new file mode 100644 index 000000000..216e97ce0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/world.txt @@ -0,0 +1 @@ +World diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index index abbebf181..1d91f96f1 100644 Binary files a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD deleted file mode 100644 index 9e2a5c74a..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,9 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902455 -0500 commit (initial): Initial commit -a3ccc66533922b02858da9ed489cd19d573d6378 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902574 -0500 commit: Second commit on master -83cebf5389a4adbcb80bda6b68513caee4559802 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902595 -0500 checkout: moving from master to fast_forward -83cebf5389a4adbcb80bda6b68513caee4559802 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 Jameson Miller 1393902643 -0500 commit: Commit on fast_forward -4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902655 -0500 checkout: moving from fast_forward to normal_merge -a3ccc66533922b02858da9ed489cd19d573d6378 625186280ed2a6ec9b65d250ed90cf2e4acef957 Jameson Miller 1393902725 -0500 commit: Commit on normal_merge -625186280ed2a6ec9b65d250ed90cf2e4acef957 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902735 -0500 checkout: moving from normal_merge to conflicts -a3ccc66533922b02858da9ed489cd19d573d6378 0771966a0d112d3787006a6fda5b6226a770e15a Jameson Miller 1393902760 -0500 commit: Commit on conflicts -0771966a0d112d3787006a6fda5b6226a770e15a 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393907470 -0500 checkout: moving from conflicts to master diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts deleted file mode 100644 index bf17c1056..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/conflicts +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902539 -0500 branch: Created from master -a3ccc66533922b02858da9ed489cd19d573d6378 0771966a0d112d3787006a6fda5b6226a770e15a Jameson Miller 1393902760 -0500 commit: Commit on conflicts diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward deleted file mode 100644 index 9b3d80c28..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/fast_forward +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902591 -0500 branch: Created from master -83cebf5389a4adbcb80bda6b68513caee4559802 4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 Jameson Miller 1393902643 -0500 commit: Commit on fast_forward diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 8201a1408..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902455 -0500 commit (initial): Initial commit -a3ccc66533922b02858da9ed489cd19d573d6378 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1393902574 -0500 commit: Second commit on master diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge deleted file mode 100644 index e17413bdb..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/normal_merge +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 a3ccc66533922b02858da9ed489cd19d573d6378 Jameson Miller 1393902530 -0500 branch: Created from master -a3ccc66533922b02858da9ed489cd19d573d6378 625186280ed2a6ec9b65d250ed90cf2e4acef957 Jameson Miller 1393902725 -0500 commit: Commit on normal_merge diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename deleted file mode 100644 index a2466a9d2..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename +++ /dev/null @@ -1,3 +0,0 @@ -0000000000000000000000000000000000000000 83cebf5389a4adbcb80bda6b68513caee4559802 Jameson Miller 1398366152 -0400 branch: Created from HEAD -83cebf5389a4adbcb80bda6b68513caee4559802 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366254 -0400 commit: Add content to b.txt -9075c06ff9cd736610dea688bca7e912903ff2d1 24434077dec097c1203ef9e1345c0545c190936a Jameson Miller 1398366641 -0400 commit: edit to b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source deleted file mode 100644 index 1515dd26e..000000000 --- a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/logs/refs/heads/rename_source +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 9075c06ff9cd736610dea688bca7e912903ff2d1 Jameson Miller 1398366267 -0400 branch: Created from rename -9075c06ff9cd736610dea688bca7e912903ff2d1 2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 Jameson Miller 1398366593 -0400 commit: rename and edit b.txt diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 new file mode 100644 index 000000000..77e845bdc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/09/55f5468a8e493c0116872fa3e1807559bd5bb2 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd new file mode 100644 index 000000000..81a42108b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/1e/b39b79963b9acb87bb1b76a218d862b689f4cd differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 new file mode 100644 index 000000000..5eba30ecd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/79/853dbb13f5e83a1e9e69bf747c5a667c21d420 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be new file mode 100644 index 000000000..12b03deec Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/96/adea09cb7c3e9e81e488fbf0bc29e61d8100be differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 new file mode 100644 index 000000000..501f4f309 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/cf/2badd0f483ffe19755560e6f736f1b4621f737 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 new file mode 100644 index 000000000..a10105db5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/e5/5b31220c73a5535ba58709791880f3035849d4 differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces new file mode 100644 index 000000000..49be02a92 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_spaces @@ -0,0 +1 @@ +79853dbb13f5e83a1e9e69bf747c5a667c21d420 diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs new file mode 100644 index 000000000..8a2211064 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/conflicts_tabs @@ -0,0 +1 @@ +e55b31220c73a5535ba58709791880f3035849d4 diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config index 78387c50b..277bd5530 100644 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config +++ b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/config @@ -6,3 +6,4 @@ symlinks = false ignorecase = true hideDotFiles = dotGitOnly + autocrlf = false diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD deleted file mode 100644 index 839dfc811..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,15 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632480 -0600 commit (initial): ancestor -9107b30fe11ff310fec93876469147b263fb958c 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632483 -0600 checkout: moving from master to branch -9107b30fe11ff310fec93876469147b263fb958c d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360632833 -0600 commit: branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632835 -0600 checkout: moving from branch to master -9107b30fe11ff310fec93876469147b263fb958c 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633224 -0600 commit: ours -8b19fd77de5995e6e502e7c3b82657248f510325 91116bad97c50a2daa278ca6cffe16d0118befe5 Edward Thomson 1360633337 -0600 commit: ours -91116bad97c50a2daa278ca6cffe16d0118befe5 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633358 -0600 checkout: moving from master to 8b19fd7 -8b19fd77de5995e6e502e7c3b82657248f510325 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633359 -0600 rebase -i (squash): ours -9b2f98b1c086d3929eac387d39fae94425e0115f 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633362 -0600 rebase -i (finish): returning to refs/heads/master -9b2f98b1c086d3929eac387d39fae94425e0115f d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360633367 -0600 checkout: moving from master to branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 493209f496e504d63fd78e8c5b800083b442ea34 Edward Thomson 1360633416 -0600 commit: branch -493209f496e504d63fd78e8c5b800083b442ea34 d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360633427 -0600 checkout: moving from branch to d4c1075 -d4c107581ff54a41fd496e0a1f1b7b3354752103 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633429 -0600 rebase -i (squash): branch -8eefb6f8061e4d10a7a59aac8771dc17958d93eb 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633436 -0600 rebase -i (finish): returning to refs/heads/branch -8eefb6f8061e4d10a7a59aac8771dc17958d93eb 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633440 -0600 checkout: moving from branch to master diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch deleted file mode 100644 index 13819577f..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/branch +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632483 -0600 branch: Created from HEAD -9107b30fe11ff310fec93876469147b263fb958c d4c107581ff54a41fd496e0a1f1b7b3354752103 Edward Thomson 1360632833 -0600 commit: branch -d4c107581ff54a41fd496e0a1f1b7b3354752103 493209f496e504d63fd78e8c5b800083b442ea34 Edward Thomson 1360633416 -0600 commit: branch -493209f496e504d63fd78e8c5b800083b442ea34 8eefb6f8061e4d10a7a59aac8771dc17958d93eb Edward Thomson 1360633436 -0600 rebase -i (finish): refs/heads/branch onto d4c1075 diff --git a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 167ae3e56..000000000 --- a/LibGit2Sharp.Tests/Resources/mergedrepo_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 9107b30fe11ff310fec93876469147b263fb958c Edward Thomson 1360632480 -0600 commit (initial): ancestor -9107b30fe11ff310fec93876469147b263fb958c 8b19fd77de5995e6e502e7c3b82657248f510325 Edward Thomson 1360633224 -0600 commit: ours -8b19fd77de5995e6e502e7c3b82657248f510325 91116bad97c50a2daa278ca6cffe16d0118befe5 Edward Thomson 1360633337 -0600 commit: ours -91116bad97c50a2daa278ca6cffe16d0118befe5 9b2f98b1c086d3929eac387d39fae94425e0115f Edward Thomson 1360633362 -0600 rebase -i (finish): refs/heads/master onto 8b19fd7 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD deleted file mode 100644 index 7b5293fd3..000000000 --- a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 bd593285fc7fe4ca18ccdbabf027f5d689101452 Edward Thomson 1365715620 -0500 moving to bd59328 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index a46518da8..000000000 --- a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 977c696519c5a3004c5f1d15d60c89dbeb8f235f Edward Thomson 1351875584 -0500 reset: moving to 977c696519c5a3004c5f1d15d60c89dbeb8f235f diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index new file mode 100644 index 000000000..1af67f2d4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 new file mode 100644 index 000000000..3c800a797 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/87/2129051d644790636b416d1ef1ec830c5f6b90 @@ -0,0 +1,3 @@ +xI +B1]>'[ oU/Zni"5SQhC.n=? \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b new file mode 100644 index 000000000..783449ff9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/88/e38705fdbd3608cddbe904b67c731f3234c45b differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 new file mode 100644 index 000000000..6b011038d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a new file mode 100644 index 000000000..6802d4949 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a differ diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..2ed6cd9fa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +872129051d644790636b416d1ef1ec830c5f6b90 diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt new file mode 100644 index 000000000..e965047ad --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/hello.txt @@ -0,0 +1 @@ +Hello diff --git a/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt new file mode 100644 index 000000000..216e97ce0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/packbuilder_testrepo_wd/world.txt @@ -0,0 +1 @@ +World diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/.gitmodules b/LibGit2Sharp.Tests/Resources/submodule_small_wd/.gitmodules new file mode 100644 index 000000000..70b60cce8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodule_target_wd"] + path = submodule_target_wd + url = ../submodule_target_wd diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/index new file mode 100644 index 000000000..6dbbec61a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/info/exclude similarity index 100% rename from LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/info/exclude rename to LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/info/exclude diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/07/406ba95d0a865a508910605a0c7d7a773705f6 b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/07/406ba95d0a865a508910605a0c7d7a773705f6 new file mode 100644 index 000000000..f6a8cc082 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/07/406ba95d0a865a508910605a0c7d7a773705f6 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/31/be1072bafa80f8b4386eb35b90e284d9ee672d b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/31/be1072bafa80f8b4386eb35b90e284d9ee672d new file mode 100644 index 000000000..ad7bc249e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/31/be1072bafa80f8b4386eb35b90e284d9ee672d differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/63/2218a6cacefe3b49ed111704df72ea7b08b4d5 b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/63/2218a6cacefe3b49ed111704df72ea7b08b4d5 new file mode 100644 index 000000000..c136428d0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/63/2218a6cacefe3b49ed111704df72ea7b08b4d5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/75/df0ce58d6436371c3c2271649a03f540f30fd6 b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/75/df0ce58d6436371c3c2271649a03f540f30fd6 new file mode 100644 index 000000000..9180f5e35 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/75/df0ce58d6436371c3c2271649a03f540f30fd6 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/7d/3aeaa887efe60fb664ba92a203a4fb50249f5b b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/7d/3aeaa887efe60fb664ba92a203a4fb50249f5b new file mode 100644 index 000000000..ff8693e3c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/objects/7d/3aeaa887efe60fb664ba92a203a4fb50249f5b differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/alternate b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/alternate new file mode 100644 index 000000000..60990de61 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/alternate @@ -0,0 +1 @@ +31be1072bafa80f8b4386eb35b90e284d9ee672d diff --git a/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..928414739 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +7d3aeaa887efe60fb664ba92a203a4fb50249f5b diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD deleted file mode 100644 index 0ecd1113f..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit -6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file -41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file -5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 0ecd1113f..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 6b31c659545507c381e9cd34ec508f16c04e149e Russell Belfer 1342559662 -0700 commit (initial): Initial commit -6b31c659545507c381e9cd34ec508f16c04e149e 41bd4bc3df978de695f67ace64c560913da11653 Russell Belfer 1342559709 -0700 commit: Adding test file -41bd4bc3df978de695f67ace64c560913da11653 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559726 -0700 commit: Updating test file -5e4963595a9774b90524d35a807169049de8ccad 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342559925 -0700 commit: One more update diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config index d8535d3ac..7eada4ff5 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config @@ -18,3 +18,5 @@ url = ../submodule_target_wd [submodule "sm_added_and_uncommited"] url = ../submodule_target_wd +[submodule "sm_branch_only"] + url = ../submodule_target_wd diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD deleted file mode 100644 index 2cf2ca74d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/HEAD +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit -14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule -a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule -5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master deleted file mode 100644 index 2cf2ca74d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/logs/refs/heads/master +++ /dev/null @@ -1,4 +0,0 @@ -0000000000000000000000000000000000000000 14fe9ccf104058df25e0a08361c4494e167ef243 Russell Belfer 1342559771 -0700 commit (initial): Initial commit -14fe9ccf104058df25e0a08361c4494e167ef243 a9104bf89e911387244ef499413960ba472066d9 Russell Belfer 1342559831 -0700 commit: Adding a submodule -a9104bf89e911387244ef499413960ba472066d9 5901da4f1c67756eeadc5121d206bec2431f253b Russell Belfer 1342560036 -0700 commit: Updating submodule -5901da4f1c67756eeadc5121d206bec2431f253b 7484482eb8db738cafa696993664607500a3f2b9 Russell Belfer 1342560288 -0700 commit: Adding a bunch more test content diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config index 2d0583e99..e37936d26 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 53753e7dd..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560316 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config new file mode 100644 index 000000000..d00ee8884 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/config @@ -0,0 +1,16 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + worktree = ../../../sm_branch_only + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + url = ../../../../submodule_target_wd + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master + rebase = true diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index new file mode 100644 index 000000000..a30232ec4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/6b/31c659545507c381e9cd34ec508f16c04e149e @@ -0,0 +1,2 @@ +xQ +!Evoy*_@g#hOh^9wSòf1*[Ic Ԥpk Α\S߇l@.^QpF(:D5zr~ en8 \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a @@ -0,0 +1,2 @@ +x-10 Fa0p(N-ӡғq]>ks*? |m“i@mV'`).-1 x +uxt(+ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/78/9efbdadaa4a582778d4584385495559ea0994b @@ -0,0 +1,2 @@ +x 0 )?= NlOkj8&r +qJW7B<fK8#Q1C-"e̫>'@ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs new file mode 100644 index 000000000..def303a5f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled fully-peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_branch_only/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config index 10cc2508e..9812c64b3 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD deleted file mode 100644 index e5cb63f8d..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560173 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG deleted file mode 100644 index 6b8d1e3fc..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/COMMIT_EDITMSG +++ /dev/null @@ -1 +0,0 @@ -Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config index 7d002536a..6d46e37ac 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# 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/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD deleted file mode 100644 index cabdeb2b5..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/HEAD +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target -480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master deleted file mode 100644 index cabdeb2b5..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/heads/master +++ /dev/null @@ -1,2 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target -480095882d281ed676fe5b863569520e54a7d5c0 3d9386c507f6b093471a3e324085657a3c2b4247 Russell Belfer 1342560431 -0700 commit: Making a change in a submodule diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 257ca21d1..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560179 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config index 0274ff7e3..94490e330 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# 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/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 80eb54102..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560175 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config index 7f2584476..e5ff0780a 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# 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/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD deleted file mode 100644 index d1beafbd6..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560186 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config index 45fbb30cf..376a475b5 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# 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/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD deleted file mode 100644 index ee08c9706..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 5e4963595a9774b90524d35a807169049de8ccad Russell Belfer 1342559796 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config index fc706c9dd..ea79f0e26 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config @@ -7,7 +7,7 @@ ignorecase = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* - url = /Users/rb/src/libgit2/tests-clar/resources/submod2_target + url = ../../../../submodule_target_wd [branch "master"] remote = origin merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude deleted file mode 100644 index a5196d1be..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/info/exclude +++ /dev/null @@ -1,6 +0,0 @@ -# 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/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD deleted file mode 100644 index 72653286a..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/logs/refs/remotes/origin/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 480095882d281ed676fe5b863569520e54a7d5c0 Russell Belfer 1342560169 -0700 clone: from /Users/rb/src/libgit2/tests-clar/resources/submod2_target diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 new file mode 100644 index 000000000..6f03cbf00 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/13/921096a46cb66610badba272f8211346eaf8f3 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c new file mode 100644 index 000000000..21bbe6d2b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/6b/94d06e586d4ed904d8c00a9de7d0afe0fc9c3c differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf new file mode 100644 index 000000000..666341811 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/aa3a079302a662a9226af3c6e7f444815e3faf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a new file mode 100644 index 000000000..34675b82e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a4/aab482be687d2facec638781ded4aa1a92687a differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 new file mode 100644 index 000000000..d620874b9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/cf/27c0500009f6d12fd82d841ecf6a17b18ff812 @@ -0,0 +1,2 @@ +xK `)HP“Cz{jS-?fHCf]\ jJ8'4ǝQJxG{U2&mr֙t׃挦x\-|4Yf +Mmej dНчNY4ڠ)c6"܍\9l}2X;7N?Y h \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev new file mode 100644 index 000000000..9cbe3f817 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/refs/heads/dev @@ -0,0 +1 @@ +6b94d06e586d4ed904d8c00a9de7d0afe0fc9c3c diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description deleted file mode 100644 index 498b267a8..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD deleted file mode 100644 index 1749e7dff..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/HEAD +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master deleted file mode 100644 index 1749e7dff..000000000 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/not_submodule/dot_git/logs/refs/heads/master +++ /dev/null @@ -1 +0,0 @@ -0000000000000000000000000000000000000000 68e92c611b80ee1ed8f38314ff9577f0d15b2444 Russell Belfer 1342560358 -0700 commit (initial): Initial commit diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description deleted file mode 100644 index 893e5cad2..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/description +++ /dev/null @@ -1 +0,0 @@ -Unnamed repository; edit this file 'description' to name the repository. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt new file mode 100644 index 000000000..5626abf0f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1.txt @@ -0,0 +1 @@ +one diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/1/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README new file mode 100644 index 000000000..a8233120f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/README @@ -0,0 +1 @@ +hey there diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt new file mode 100644 index 000000000..45b983be3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG new file mode 100644 index 000000000..63ec8fdda --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/COMMIT_EDITMSG @@ -0,0 +1 @@ +Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb4380516 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config new file mode 100644 index 000000000..1599f0b76 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/config @@ -0,0 +1,23 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + url = c:/GitHub/libgit2sharp/Resources/testrepo.git +[remote "no_url"] + url = + fetch = +refs/heads/*:refs/remotes/no_url/* +[branch "master"] + remote = origin + merge = refs/heads/master +[branch "track-local"] + remote = . + merge = refs/heads/master +[unittests] + longsetting = 15234 + intsetting = 2 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index new file mode 100644 index 000000000..1f5bd73a7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude similarity index 97% rename from LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude rename to LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude index a5196d1be..f00680973 100644 --- a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/info/exclude +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude @@ -1,6 +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] -# *~ +# 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/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD new file mode 100644 index 000000000..23375c60c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/HEAD @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 Tim Clem 1303768198 -0700 clone: from c:/GitHub/libgit2sharp/Resources/testrepo.git +4c062a6361ae6959e06292c1fa5e2822d9c96345 592d3c869dbc4127fc57c189cb94f2794fa84e7e Tim Clem 1303835722 -0700 commit: add more test files +592d3c869dbc4127fc57c189cb94f2794fa84e7e 32eab9cb1f450b5fe7ab663462b77d7f4b703344 nulltoken 1320047537 +0100 commit: Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo new file mode 100644 index 000000000..e3e3e53ff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/logo @@ -0,0 +1,2 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 nulltoken 1359021419 +0100 branch: Created from 4c062a6361ae6959e06292c1fa5e2822d9c96345 +4c062a6361ae6959e06292c1fa5e2822d9c96345 a447ba2ca8fffd46dece72f7db6faf324afb8fcd nulltoken 1359021433 +0100 commit: Add logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master new file mode 100644 index 000000000..23375c60c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/logs/refs/heads/master @@ -0,0 +1,3 @@ +0000000000000000000000000000000000000000 4c062a6361ae6959e06292c1fa5e2822d9c96345 Tim Clem 1303768198 -0700 clone: from c:/GitHub/libgit2sharp/Resources/testrepo.git +4c062a6361ae6959e06292c1fa5e2822d9c96345 592d3c869dbc4127fc57c189cb94f2794fa84e7e Tim Clem 1303835722 -0700 commit: add more test files +592d3c869dbc4127fc57c189cb94f2794fa84e7e 32eab9cb1f450b5fe7ab663462b77d7f4b703344 nulltoken 1320047537 +0100 commit: Add "1.txt" file beside "1" folder diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 new file mode 100644 index 000000000..a8660a9ea Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 new file mode 100644 index 000000000..560fa54b9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/0a/99448e920a3615f33273047412949d09015ff8 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 new file mode 100644 index 000000000..cedb2a22e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/13/85f264afb75a56a5bec74243be9b367ba4ca08 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 new file mode 100644 index 000000000..ec005aa90 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/15/d2ecc60893449f4fe4593dd51a4706dec212f5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a new file mode 100644 index 000000000..6f579f741 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/16/bdf1dece5c56c92a9187550fafe0270a03a93a @@ -0,0 +1,2 @@ +x ̱ +1EQjm\,Dl,'Ä:"큛3^47uو \0yVg(Wϝ XmL?2ʍjK=yPK™I #Y \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 new file mode 100644 index 000000000..93a16f146 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/1037049a54a1eb5fab404658a3a250b44335d7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd new file mode 100644 index 000000000..ba0bfb30c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/18/10dff58d8a660512d4832e740f692884338ccd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 new file mode 100644 index 000000000..9690a6ad0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/32/eab9cb1f450b5fe7ab663462b77d7f4b703344 @@ -0,0 +1 @@ +x]j!)} ?!dm+qq]3䥠>Z&0M1h}VvY+.a9z4O *LGxxdVðc^Zg(e-<n-Qo߀kBgORE5%6r %bx7GP diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 new file mode 100644 index 000000000..95151430c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c new file mode 100644 index 000000000..0598edb69 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/37/d22f870ffe688c0d1220fbbf1f06629c64142c differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 new file mode 100644 index 000000000..7ca4ceed5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/45/b983be36b73c0788dc9cbcb76cbb80fc7bb057 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 new file mode 100644 index 000000000..500ff4036 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/46/25a3628cb78970c57e23a2fe2574514ba403c7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 new file mode 100644 index 000000000..8953b6cef --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4a/202b346bb0fb0db7eff3cffeb3c70babbd2045 @@ -0,0 +1,2 @@ +xQ +0D)6ͦ "xO-FbEo0 Ǥ,ske[Pn8R,EpD?g}^3 <GhYK8ЖDA);gݧjp4-r;sGA4ۺ=(in7IKFE \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 new file mode 100644 index 000000000..e1ab3daf0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4b/e51d6fc0943aa42b635c762145ca209cf39771 @@ -0,0 +1,2 @@ +xK +1D] C tg&`&QVkQFgauxved0Sr.yJ!'C^! @`2,@ ({Oi_eO\VRag):w>R-&BEy \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 new file mode 100644 index 000000000..4d86b3208 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4c/062a6361ae6959e06292c1fa5e2822d9c96345 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e new file mode 100644 index 000000000..c48084358 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/4e/935b73cf0fe06c513267d517fc2e65fc0d100e differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f new file mode 100644 index 000000000..b3fac2ef1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/50/9d02afef0632c7f733ddcd62500b0538d9157f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad new file mode 100644 index 000000000..ecaf0c6fe Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/05472eb48cb4e60b5aa8810cc5ec8138026fad differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 new file mode 100644 index 000000000..4d5447467 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/56/26abf0f72e58d7a153368ba57db4c673c0e171 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 new file mode 100644 index 000000000..81671a754 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 @@ -0,0 +1,2 @@ +xAj1 E)t{d[6EvG ep}B{>B0I \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 new file mode 100644 index 000000000..2ae137844 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/58/1f9824ecaf824221bd36edf5430f2739a7c4f5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e new file mode 100644 index 000000000..33d011c41 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/59/2d3c869dbc4127fc57c189cb94f2794fa84e7e differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 new file mode 100644 index 000000000..c1f22c54f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/5b/5b025afb0b4c913b4c338a42934a3863bf3644 @@ -0,0 +1,2 @@ +x 1ENi@k2 "X$YW0YcÅszMD08!s Xgd::@X0Pw"F/RUzmZZV}|/o5I!1z:vUim}/> +F- \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd new file mode 100644 index 000000000..8c4a65994 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/67/b8324ec2fefc01fd9d31d328116df0474e7acd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 new file mode 100644 index 000000000..c0685b971 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/6b/53f5d357f29607605ce2e612d2fda6693ff8d7 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a new file mode 100644 index 000000000..15682ca4d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/72/52fe2da2c4dd6d85231a150d0485ec46abaa7a differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 new file mode 100644 index 000000000..dbebba8e4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a new file mode 100644 index 000000000..2ef4faa0f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/75/057dd4114e74cca1d750d0aee1647c903cb60a differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 new file mode 100644 index 000000000..a1f7d97f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/79/09961ae96accd75b6813d32e0fc1d6d52ec941 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 new file mode 100644 index 000000000..23c462f34 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7b/4384978d2493e851f9cca7858815fac9b10980 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f new file mode 100644 index 000000000..7018c7f77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/7f/76480d939dc401415927ea7ef25c676b8ddb8f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d new file mode 100644 index 000000000..2f9b6b6e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/81/4889a078c031f61ed08ab5fa863aea9314344d differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 new file mode 100644 index 000000000..5df58dda5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/96071c1b46c854b31185ea97743be6a8774479 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f new file mode 100644 index 000000000..eb5f7b230 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/84/9f67c87f926a81af895fc037c04ad85549d73f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b new file mode 100644 index 000000000..89e7566cd --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/89/657cd6da3ada7bfef880e6dfdb9732f28c272b @@ -0,0 +1,2 @@ +xA + E/cJ=8TA#]M ϭQvL0I?!4Z=!צ8F!rsQy9]$D&l6A>jFWҵ IKNiZ%S + U~̽>' w [ DGڡQ-M>dO}\8g_ШoYr \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd new file mode 100644 index 000000000..d0d7e736e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a7/1586c1dfe8a71c6cbf6c129f404c5642ff31bd differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 new file mode 100644 index 000000000..18a7f61c2 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/a8/233120f6ad708f843d861ce2b7228ec4e3dec6 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 new file mode 100644 index 000000000..649e9bbfa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ab/38987d12dc243c103a432608648c78fc6651a1 @@ -0,0 +1,2 @@ +xM +0F] eDzq@@ oo {Rré 1EȀKƀ<|V~ƛ|L|ܞ>I-Ws@Ԡ\"KZ Gw: \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 new file mode 100644 index 000000000..f460f2547 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b2/5fa35b38051e4ae45d4222e795f9df2e43f1d1 @@ -0,0 +1,2 @@ +xA +0a9I p'1Ѷv\x{cVpvWgǎ0x[ ]"g#{rD Cot N U $?9-p+1^Qx9O\C m'D {mV(+l, \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 new file mode 100644 index 000000000..511e72c6a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f new file mode 100644 index 000000000..eed20b8e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 new file mode 100644 index 000000000..ee6419e03 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/b7/58c5bc1c8117c2a4c545dae2903e36360501c5 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 new file mode 100644 index 000000000..0817229bc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/be/3563ae3f795b2b4353bcce3a527ad0a4f7f644 @@ -0,0 +1,3 @@ +xKj1D)zUB-0uV9<#+W e^7t:wo܂ p@.=..nD"JHqDV1tUeޕi n afu9FkcOe׿*qk9rL^"!ay%_2fw3G_K \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 new file mode 100644 index 000000000..16eca526d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/e8/953ab38d30b11c45b5ac7229fcef0ab4d603c6 @@ -0,0 +1 @@ +x A@0P[!N#ϋz]ld uE/DnDT$hXϼzp0(=bhj73|e~#[ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d new file mode 100644 index 000000000..9ba063ec3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/ec/9e401198937e33a8617be9f235a449728d9f6d @@ -0,0 +1,4 @@ +xA +0E]$ fL#1 +>ǃ?ScU`=J'DdQ)xFDGު'WD¨0x2L-Z#qbm-> +n呶ے9=+hG7B3jDuaZuO-[WcT_FHn \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 new file mode 100644 index 000000000..1cccf6543 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f2/e41136eac73c39554dede1fd7e67b12502d577 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 new file mode 100644 index 000000000..03770969a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f6/0079018b664e4e79329a7ef9559c8d9e0378d1 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e new file mode 100644 index 000000000..7490425a2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e @@ -0,0 +1 @@ +x B!D=S h؅Bb;XGc|/Kdz-FѲDXy) Y1X4z.rdv4Mbst+ҌS/zkuk}I\qVOlm QΣCPp1J \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 new file mode 100644 index 000000000..639e89094 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/f8/d44d712e0680d942a4015058dd84e382879fe2 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 new file mode 100644 index 000000000..112998d42 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fa/49b077972391ad58037050f2a75f74e3671e92 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 new file mode 100644 index 000000000..12bf5f3e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/fd/093bff70906175335656e6ce6ae05783708765 differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx new file mode 100644 index 000000000..5068f2818 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack new file mode 100644 index 000000000..a6a1f3020 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-a81e489679b7d3418f9ab594bda8ceb37dd4c695.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx new file mode 100644 index 000000000..94c3c71da Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack new file mode 100644 index 000000000..74c7fe4f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d7c6adf9f61318f041845b01440d09aa7a91e1b5.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx new file mode 100644 index 000000000..555cfa977 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.idx differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack new file mode 100644 index 000000000..4d539ed0a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/objects/pack/pack-d85f5d483273108c9d8dd0e4728ccf0b2982423a.pack differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs new file mode 100644 index 000000000..71a7668ba --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/packed-refs @@ -0,0 +1,10 @@ +# pack-refs with: peeled +b25fa35b38051e4ae45d4222e795f9df2e43f1d1 refs/tags/test +^e90810b8df3e80c413d903f631643c716887138d +e90810b8df3e80c413d903f631643c716887138d refs/tags/lw +7b4384978d2493e851f9cca7858815fac9b10980 refs/tags/e90810b +^e90810b8df3e80c413d903f631643c716887138d +e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test +4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test +580c2111be43802dab11328176d94c391f1deae9 refs/remotes/origin/master +a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases new file mode 100644 index 000000000..f385e58ba --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/diff-test-cases @@ -0,0 +1 @@ +e7039e6d0e7dd4d4c1e2e8e5aa5306b2776436ca diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers new file mode 100644 index 000000000..882969dfc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/i-do-numbers @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo new file mode 100644 index 000000000..2bbca050f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/logo @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..bca334acf --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +32eab9cb1f450b5fe7ab663462b77d7f4b703344 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local new file mode 100644 index 000000000..99098dc82 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/track-local @@ -0,0 +1 @@ +580c2111be43802dab11328176d94c391f1deae9 diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab new file mode 100644 index 000000000..2f412c398 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/heads/treesame_as_32eab @@ -0,0 +1 @@ +f705abffe7015f2beacf2abe7a36583ebee3487e diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD new file mode 100644 index 000000000..b827f0c47 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD new file mode 100644 index 000000000..43d92a0f8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/HEAD @@ -0,0 +1 @@ +ref: refs/heads/i-do-numbers diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD new file mode 100644 index 000000000..882969dfc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/ORIG_HEAD @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir new file mode 100644 index 000000000..aab0408ce --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/commondir @@ -0,0 +1 @@ +../.. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir new file mode 100644 index 000000000..3644e42d1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/gitdir @@ -0,0 +1 @@ +../../../../worktrees/i-do-numbers/.git diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index new file mode 100644 index 000000000..d8a77a021 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/index differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD new file mode 100644 index 000000000..f08b3ba25 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/i-do-numbers/logs/HEAD @@ -0,0 +1 @@ +7252fe2da2c4dd6d85231a150d0485ec46abaa7a 7252fe2da2c4dd6d85231a150d0485ec46abaa7a Mike Minns 1513714384 +0000 reset: moving to HEAD diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD new file mode 100644 index 000000000..846e685a7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/HEAD @@ -0,0 +1 @@ +ref: refs/heads/logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD new file mode 100644 index 000000000..2bbca050f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/ORIG_HEAD @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir new file mode 100644 index 000000000..aab0408ce --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/commondir @@ -0,0 +1 @@ +../.. diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir new file mode 100644 index 000000000..ad8863ee6 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/gitdir @@ -0,0 +1 @@ +../../../../worktrees/logo/.git diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index new file mode 100644 index 000000000..2b8b35b77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/index differ diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked new file mode 100644 index 000000000..9f0b8bf32 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/locked @@ -0,0 +1 @@ +Test lock reason diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD new file mode 100644 index 000000000..ab8778340 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/worktrees/logo/logs/HEAD @@ -0,0 +1 @@ +a447ba2ca8fffd46dece72f7db6faf324afb8fcd a447ba2ca8fffd46dece72f7db6faf324afb8fcd Mike Minns 1513713776 +0000 reset: moving to HEAD diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt new file mode 100644 index 000000000..e68bcc7b5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_staged_file.txt @@ -0,0 +1,2 @@ +a change +more files! diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt new file mode 100644 index 000000000..da6fd6537 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/modified_unstaged_file.txt @@ -0,0 +1,2 @@ +some more text +more files! more files! diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt new file mode 100644 index 000000000..a71586c1d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new.txt @@ -0,0 +1 @@ +my new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt new file mode 100644 index 000000000..935a81d39 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_tracked_file.txt @@ -0,0 +1 @@ +a new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt new file mode 100644 index 000000000..d95f3ad14 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/new_untracked_file.txt @@ -0,0 +1 @@ +content diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git new file mode 100644 index 000000000..c14c3a26c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/dot_git @@ -0,0 +1 @@ +gitdir: ../../testrepo_wd/.git/worktrees/i-do-numbers diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt new file mode 100644 index 000000000..85e1bcbc0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/numbers.txt @@ -0,0 +1,17 @@ +1 +2 +3 +4 +5 +6 +7 +7.2 +8 +9 +10 +11 +12 +13 +14 +15 +16 diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt new file mode 100644 index 000000000..f9ff5589e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/i-do-numbers/super-file.txt @@ -0,0 +1,5 @@ +That's a terrible name! +I don't like it. +People look down at me and laugh. :-( +Really!!!! +Yeah! Better! diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt new file mode 100644 index 000000000..edf0effbb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/1/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README new file mode 100644 index 000000000..ca8c64728 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/README @@ -0,0 +1 @@ +hey there diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt new file mode 100644 index 000000000..edf0effbb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/branch_file.txt @@ -0,0 +1 @@ +hi diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git new file mode 100644 index 000000000..8295ccb37 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/dot_git @@ -0,0 +1 @@ +gitdir: ../../testrepo_wd/.git/worktrees/logo diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt new file mode 100644 index 000000000..8e0884e36 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/new.txt @@ -0,0 +1 @@ +my new file diff --git a/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png new file mode 100644 index 000000000..b758c5bc1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/worktree/worktrees/logo/square-logo.png differ diff --git a/LibGit2Sharp.Tests/RevertFixture.cs b/LibGit2Sharp.Tests/RevertFixture.cs index 13ab2c3a4..c43479f0f 100644 --- a/LibGit2Sharp.Tests/RevertFixture.cs +++ b/LibGit2Sharp.Tests/RevertFixture.cs @@ -1,9 +1,9 @@ +using System; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; -using System; namespace LibGit2Sharp.Tests { @@ -17,11 +17,11 @@ public void CanRevert() const string revertBranchName = "refs/heads/revert"; const string revertedFile = "a.txt"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Revert tip commit. @@ -37,7 +37,7 @@ public void CanRevert() // Verify workspace is clean. Assert.True(repo.Index.IsFullyMerged); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Lookup the blob containing the expected reverted content of a.txt. Blob expectedBlob = repo.Lookup("bc90ea420cf6c5ae3db7dcdffa0d79df567f219b"); @@ -64,13 +64,11 @@ public void CanRevertAndNotCommit() const string revertBranchName = "refs/heads/revert"; const string revertedFile = "a.txt"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { - string modifiedFileFullPath = Path.Combine(repo.Info.WorkingDirectory, revertedFile); - // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Revert tip commit. @@ -82,8 +80,8 @@ public void CanRevertAndNotCommit() Assert.Null(result.Commit); // Verify workspace is dirty. - FileStatus fileStatus = repo.Index.RetrieveStatus(revertedFile); - Assert.Equal(FileStatus.Staged, fileStatus); + FileStatus fileStatus = repo.RetrieveStatus(revertedFile); + Assert.Equal(FileStatus.ModifiedInIndex, fileStatus); // This is the ID of the blob containing the expected content. Blob expectedBlob = repo.Lookup("bc90ea420cf6c5ae3db7dcdffa0d79df567f219b"); @@ -108,11 +106,11 @@ public void RevertWithConflictDoesNotCommit() // and the file whose contents we expect to be reverted. const string revertBranchName = "refs/heads/revert"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // The commit to revert - we know that reverting this @@ -131,8 +129,8 @@ public void RevertWithConflictDoesNotCommit() Assert.NotNull(repo.Index.Conflicts["a.txt"]); // Verify the non-conflicting paths are staged. - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus("b.txt")); - Assert.Equal(FileStatus.Staged, repo.Index.RetrieveStatus("c.txt")); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("b.txt")); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("c.txt")); } } @@ -146,11 +144,11 @@ public void RevertWithFileConflictStrategyOption(CheckoutFileConflictStrategy co const string revertBranchName = "refs/heads/revert"; const string conflictedFilePath = "a.txt"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); // Specify FileConflictStrategy. @@ -159,7 +157,8 @@ public void RevertWithFileConflictStrategyOption(CheckoutFileConflictStrategy co FileConflictStrategy = conflictStrategy, }; - RevertResult result = repo.Revert(repo.Head.Tip.Parents.First(), Constants.Signature, options); + RevertResult result = repo.Revert(repo.Head.Tip.Parents.First(), Constants.Signature, options); + Assert.Equal(RevertStatus.Conflicts, result.Status); // Verify there is a conflict. Assert.False(repo.Index.IsFullyMerged); @@ -198,11 +197,11 @@ public void RevertReportsCheckoutProgress() { const string revertBranchName = "refs/heads/revert"; - string repoPath = CloneRevertTestRepo(); + string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); bool wasCalled = false; @@ -223,11 +222,11 @@ public void RevertReportsCheckoutNotification() { const string revertBranchName = "refs/heads/revert"; - string repoPath = CloneRevertTestRepo(); + string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); bool wasCalled = false; @@ -242,6 +241,7 @@ public void RevertReportsCheckoutNotification() repo.Revert(repo.Head.Tip, Constants.Signature, options); Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); } } @@ -263,10 +263,10 @@ public void RevertFindsRenames(bool? findRenames) const string expectedBlobId = "0ff3bbb9c8bba2291654cd64067fa417ff54c508"; const string modifiedFilePath = "d_renamed.txt"; - string repoPath = CloneRevertTestRepo(); + string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch currentBranch = repo.Checkout(revertBranchName); + Branch currentBranch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(currentBranch); Commit commitToRevert = repo.Lookup(commitIdToRevert); @@ -288,7 +288,7 @@ public void RevertFindsRenames(bool? findRenames) RevertResult result = repo.Revert(commitToRevert, Constants.Signature, options); Assert.NotNull(result); - if(!findRenames.HasValue || + if (!findRenames.HasValue || findRenames.Value == true) { Assert.Equal(RevertStatus.Reverted, result.Status); @@ -320,10 +320,10 @@ public void CanRevertMergeCommit(int mainline, string expectedId) const string revertBranchName = "refs/heads/revert_merge"; const string commitIdToRevert = "2747045"; - string repoPath = CloneRevertTestRepo(); + string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Lookup(commitIdToRevert); @@ -340,25 +340,25 @@ public void CanRevertMergeCommit(int mainline, string expectedId) Assert.Equal(RevertStatus.Reverted, result.Status); Assert.Equal(result.Commit.Sha, expectedId); - if(mainline == 1) + if (mainline == 1) { // In this case, we expect "d_renamed.txt" to be reverted (deleted), // and a.txt to match the tip of the "revert" branch. - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("d_renamed.txt")); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus("d_renamed.txt")); // This is the commit containing the expected contents of a.txt. Commit commit = repo.Lookup("b6fbb29b625aabe0fb5736da6fd61d4147e4405e"); Assert.NotNull(commit); Assert.Equal(commit["a.txt"].Target.Id, repo.Index["a.txt"].Id); } - else if(mainline == 2) + else if (mainline == 2) { // In this case, we expect "d_renamed.txt" to be preset, // and a.txt to match the tip of the master branch. // In this case, we expect "d_renamed.txt" to be reverted (deleted), // and a.txt to match the tip of the "revert" branch. - Assert.Equal(FileStatus.Unaltered, repo.Index.RetrieveStatus("d_renamed.txt")); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus("d_renamed.txt")); // This is the commit containing the expected contents of "d_renamed.txt". Commit commit = repo.Lookup("c4b5cea70e4cd5b633ed0f10ae0ed5384e8190d8"); @@ -379,10 +379,10 @@ public void CanNotRevertAMergeCommitWithoutSpecifyingTheMainlineBranch() const string revertBranchName = "refs/heads/revert_merge"; const string commitIdToRevert = "2747045"; - string repoPath = CloneRevertTestRepo(); + string repoPath = SandboxRevertTestRepo(); using (var repo = new Repository(repoPath)) { - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); var commitToRevert = repo.Lookup(commitIdToRevert); @@ -400,11 +400,11 @@ public void RevertWithNothingToRevert(bool commitOnSuccess) // The branch name to perform the revert on const string revertBranchName = "refs/heads/revert"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Head.Tip; @@ -421,7 +421,7 @@ public void RevertWithNothingToRevert(bool commitOnSuccess) new RevertOptions() { CommitOnSuccess = commitOnSuccess }); Assert.NotNull(result); - Assert.Equal(null, result.Commit); + Assert.Null(result.Commit); Assert.Equal(RevertStatus.NothingToRevert, result.Status); if (commitOnSuccess) @@ -441,11 +441,11 @@ public void RevertOrphanedBranchThrows() // The branch name to perform the revert on const string revertBranchName = "refs/heads/revert"; - string path = CloneRevertTestRepo(); + string path = SandboxRevertTestRepo(); using (var repo = new Repository(path)) { // Checkout the revert branch. - Branch branch = repo.Checkout(revertBranchName); + Branch branch = Commands.Checkout(repo, revertBranchName); Assert.NotNull(branch); Commit commitToRevert = repo.Head.Tip; @@ -458,5 +458,70 @@ public void RevertOrphanedBranchThrows() Assert.Throws(() => repo.Revert(commitToRevert, Constants.Signature)); } } + + [Fact] + public void RevertWithNothingToRevertInObjectDatabaseSucceeds() + { + // The branch name to perform the revert on + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + Commit commitToRevert = repo.Head.Tip; + + // Revert tip commit. + RevertResult result = repo.Revert(commitToRevert, Constants.Signature); + Assert.NotNull(result); + Assert.Equal(RevertStatus.Reverted, result.Status); + + var revertResult = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null); + + Assert.NotNull(revertResult); + Assert.Equal(MergeTreeStatus.Succeeded, revertResult.Status); + } + } + + [Fact] + public void RevertWithConflictReportsConflict() + { + // The branch name to perform the revert on, + // and the file whose contents we expect to be reverted. + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // The commit to revert - we know that reverting this + // specific commit will generate conflicts. + Commit commitToRevert = repo.Lookup("cb4f7f0eca7a0114cdafd8537332aa17de36a4e9"); + Assert.NotNull(commitToRevert); + + // Perform the revert and verify there were conflicts. + var result = repo.ObjectDatabase.RevertCommit(commitToRevert, repo.Branches[revertBranchName].Tip, 0, null); + Assert.NotNull(result); + Assert.Equal(MergeTreeStatus.Conflicts, result.Status); + Assert.Null(result.Tree); + } + } + + [Fact] + public void CanRevertInObjectDatabase() + { + // The branch name to perform the revert on + const string revertBranchName = "refs/heads/revert"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Revert tip commit. + var result = repo.ObjectDatabase.RevertCommit(repo.Branches[revertBranchName].Tip, repo.Branches[revertBranchName].Tip, 0, null); + Assert.Equal(MergeTreeStatus.Succeeded, result.Status); + } + } } } diff --git a/LibGit2Sharp.Tests/SetErrorFixture.cs b/LibGit2Sharp.Tests/SetErrorFixture.cs new file mode 100644 index 000000000..35ee15d26 --- /dev/null +++ b/LibGit2Sharp.Tests/SetErrorFixture.cs @@ -0,0 +1,186 @@ +using System; +using System.IO; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class SetErrorFixture : BaseFixture + { + + private const string simpleExceptionMessage = "This is a simple exception message."; + private const string aggregateExceptionMessage = "This is aggregate exception."; + private const string outerExceptionMessage = "This is an outer exception."; + private const string innerExceptionMessage = "This is an inner exception."; + private const string innerExceptionMessage2 = "This is inner exception #2."; + + private const string expectedInnerExceptionHeaderText = "Inner Exception:"; + private const string expectedAggregateExceptionHeaderText = "Contained Exception:"; + private const string expectedAggregateExceptionsHeaderText = "Contained Exceptions:"; + + [Fact] + public void FormatSimpleException() + { + Exception exceptionToThrow = new Exception(simpleExceptionMessage); + string expectedMessage = simpleExceptionMessage; + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + [Fact] + public void FormatExceptionWithInnerException() + { + Exception exceptionToThrow = new Exception(outerExceptionMessage, new Exception(innerExceptionMessage)); + + StringBuilder sb = new StringBuilder(); + sb.AppendLine(outerExceptionMessage); + sb.AppendLine(); + AppendIndentedLine(sb, expectedInnerExceptionHeaderText, 0); + AppendIndentedText(sb, innerExceptionMessage, 1); + string expectedMessage = sb.ToString(); + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + [Fact] + public void FormatAggregateException() + { + Exception exceptionToThrow = new AggregateException(aggregateExceptionMessage, new Exception(innerExceptionMessage), new Exception(innerExceptionMessage2)); + + StringBuilder sb = new StringBuilder(); +#if NETFRAMEWORK + sb.AppendLine(aggregateExceptionMessage); +#else + sb.AppendLine($"{aggregateExceptionMessage} ({innerExceptionMessage}) ({innerExceptionMessage2})"); +#endif + sb.AppendLine(); + + AppendIndentedLine(sb, expectedAggregateExceptionsHeaderText, 0); + + AppendIndentedLine(sb, innerExceptionMessage, 1); + sb.AppendLine(); + + AppendIndentedText(sb, innerExceptionMessage2, 1); + + string expectedMessage = sb.ToString(); + + AssertExpectedExceptionMessage(expectedMessage, exceptionToThrow); + } + + private void AssertExpectedExceptionMessage(string expectedMessage, Exception exceptionToThrow) + { + Exception thrownException = null; + + ObjectId id = new ObjectId("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"); + + string repoPath = InitNewRepository(); + using (var repo = new Repository(repoPath)) + { + repo.ObjectDatabase.AddBackend(new ThrowingOdbBackend(exceptionToThrow), priority: 1); + + try + { + repo.Lookup(id); + } + catch (Exception ex) + { + thrownException = ex; + } + } + + Assert.NotNull(thrownException); + Assert.Equal(expectedMessage, thrownException.Message); + } + + private void AppendIndentedText(StringBuilder sb, string text, int indentLevel) + { + sb.AppendFormat("{0}{1}", IndentString(indentLevel), text); + } + + private void AppendIndentedLine(StringBuilder sb, string text, int indentLevel) + { + sb.AppendFormat("{0}{1}{2}", IndentString(indentLevel), text, Environment.NewLine); + } + + private string IndentString(int level) + { + return new string(' ', level * 4); + } + + #region ThrowingOdbBackend + + private class ThrowingOdbBackend : OdbBackend + { + private Exception exceptionToThrow; + + public ThrowingOdbBackend(Exception exceptionToThrow) + { + this.exceptionToThrow = exceptionToThrow; + } + + protected override OdbBackendOperations SupportedOperations + { + get + { + return OdbBackendOperations.Read | + OdbBackendOperations.ReadPrefix | + OdbBackendOperations.Write | + OdbBackendOperations.WriteStream | + OdbBackendOperations.Exists | + OdbBackendOperations.ExistsPrefix | + OdbBackendOperations.ForEach | + OdbBackendOperations.ReadHeader; + } + } + + public override int Read(ObjectId oid, out UnmanagedMemoryStream data, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int ReadPrefix(string shortSha, out ObjectId id, out UnmanagedMemoryStream data, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int Write(ObjectId oid, Stream dataStream, long length, ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int WriteStream(long length, ObjectType objectType, out OdbBackendStream stream) + { + throw this.exceptionToThrow; + } + + public override bool Exists(ObjectId oid) + { + throw this.exceptionToThrow; + } + + public override int ExistsPrefix(string shortSha, out ObjectId found) + { + throw this.exceptionToThrow; + } + + public override int ReadHeader(ObjectId oid, out int length, out ObjectType objectType) + { + throw this.exceptionToThrow; + } + + public override int ReadStream(ObjectId oid, out OdbBackendStream stream) + { + throw this.exceptionToThrow; + } + + public override int ForEach(ForEachCallback callback) + { + throw this.exceptionToThrow; + } + } + + #endregion + + } +} diff --git a/LibGit2Sharp.Tests/SignatureFixture.cs b/LibGit2Sharp.Tests/SignatureFixture.cs index 978141837..e40cabd6c 100644 --- a/LibGit2Sharp.Tests/SignatureFixture.cs +++ b/LibGit2Sharp.Tests/SignatureFixture.cs @@ -35,13 +35,7 @@ public void CreatingASignatureWithBadParamsThrows() Assert.Throws(() => new Signature(null, "me@there.com", DateTimeOffset.Now)); Assert.Throws(() => new Signature(string.Empty, "me@there.com", DateTimeOffset.Now)); Assert.Throws(() => new Signature("Me", null, DateTimeOffset.Now)); - } - - [Fact] - public void CanCreateASignatureWithAnEmptyEmail() - { - var sig = new Signature("Me", string.Empty, DateTimeOffset.Now); - Assert.Equal(string.Empty, sig.Email); + Assert.Throws(() => new Signature("Me", string.Empty, DateTimeOffset.Now)); } } } diff --git a/LibGit2Sharp.Tests/StageFixture.cs b/LibGit2Sharp.Tests/StageFixture.cs index 3355645f0..c087aa7be 100644 --- a/LibGit2Sharp.Tests/StageFixture.cs +++ b/LibGit2Sharp.Tests/StageFixture.cs @@ -11,118 +11,139 @@ public class StageFixture : BaseFixture [Theory] [InlineData("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] [InlineData("README", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] - [InlineData("deleted_unstaged_file.txt", FileStatus.Missing, true, FileStatus.Removed, false, -1)] - [InlineData("modified_unstaged_file.txt", FileStatus.Modified, true, FileStatus.Staged, true, 0)] - [InlineData("new_untracked_file.txt", FileStatus.Untracked, false, FileStatus.Added, true, 1)] - [InlineData("modified_staged_file.txt", FileStatus.Staged, true, FileStatus.Staged, true, 0)] - [InlineData("new_tracked_file.txt", FileStatus.Added, true, FileStatus.Added, true, 0)] + [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, true, FileStatus.DeletedFromIndex, false, -1)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, true, FileStatus.ModifiedInIndex, true, 0)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir, false, FileStatus.NewInIndex, true, 1)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, true, FileStatus.ModifiedInIndex, true, 0)] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, true, FileStatus.NewInIndex, true, 0)] public void CanStage(string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Index.Stage(relativePath); + Commands.Stage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); - Assert.Equal(expectedStatusOnceStaged, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(expectedStatusOnceStaged, repo.RetrieveStatus(relativePath)); + } + } + + [Theory] + [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromIndex)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInIndex)] + [InlineData("new_untracked_file.txt", FileStatus.NewInIndex)] + public void StagingWritesIndex(string relativePath, FileStatus expectedStatus) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Commands.Stage(repo, relativePath); + } + + using (var repo = new Repository(path)) + { + Assert.Equal(expectedStatus, repo.RetrieveStatus(relativePath)); } } [Fact] public void CanStageTheUpdationOfAStagedFile() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; const string filename = "new_tracked_file.txt"; IndexEntry blob = repo.Index[filename]; - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); Touch(repo.Info.WorkingDirectory, filename, "brand new content"); - Assert.Equal(FileStatus.Added | FileStatus.Modified, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(filename)); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); IndexEntry newBlob = repo.Index[filename]; Assert.Equal(count, repo.Index.Count); Assert.NotEqual(newBlob.Id, blob.Id); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); } } [Theory] [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] public void StagingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileStatus status) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Index.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions() })); + Assert.Throws(() => Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions() })); } } [Theory] [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] public void CanStageAnUnknownFileWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus status) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); - Assert.DoesNotThrow(() => repo.Index.Stage(relativePath)); - Assert.DoesNotThrow(() => repo.Index.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } })); + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); } } [Theory] [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] public void StagingAnUnknownFileWithLaxExplicitPathsValidationDoesntThrow(string relativePath, FileStatus status) { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); - repo.Index.Stage(relativePath); - repo.Index.Stage(relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); } } [Fact] public void CanStageTheRemovalOfAStagedFile() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; const string filename = "new_tracked_file.txt"; Assert.NotNull(repo.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); - Assert.Equal(FileStatus.Added | FileStatus.Missing, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex | FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); Assert.Null(repo.Index[filename]); Assert.Equal(count - 1, repo.Index.Count); - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(filename)); } } @@ -132,25 +153,25 @@ public void CanStageTheRemovalOfAStagedFile() [InlineData("!bang/unit_test.txt")] public void CanStageANewFileInAPersistentManner(string filename) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(filename)); Assert.Null(repo.Index[filename]); Touch(repo.Info.WorkingDirectory, filename, "some contents"); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename)); Assert.Null(repo.Index[filename]); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); Assert.NotNull(repo.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); } using (var repo = new Repository(path)) { Assert.NotNull(repo.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); } } @@ -167,7 +188,7 @@ public void CanStageANewFileWithAFullPath(bool ignorecase) //InconclusiveIf(() => IsFileSystemCaseSensitive && ignorecase, // "Skipping 'ignorecase = true' test on case-sensitive file system."); - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { @@ -190,10 +211,10 @@ private static void AssertStage(bool? ignorecase, IRepository repo, string path) { try { - repo.Index.Stage(path); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(path)); - repo.Reset(); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(path)); + Commands.Stage(repo, path); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(path)); + repo.Index.Replace(repo.Head.Tip); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(path)); } catch (ArgumentException) { @@ -204,7 +225,7 @@ private static void AssertStage(bool? ignorecase, IRepository repo, string path) [Fact] public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorCharacters() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; @@ -213,13 +234,13 @@ public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorC Touch(repo.Info.WorkingDirectory, file, "With backward slash on Windows!"); - repo.Index.Stage(file); + Commands.Stage(repo, file); Assert.Equal(count + 1, repo.Index.Count); const string posixifiedPath = "Project/a_file.txt"; Assert.NotNull(repo.Index[posixifiedPath]); - Assert.Equal(file, repo.Index[posixifiedPath].Path); + Assert.Equal(posixifiedPath, repo.Index[posixifiedPath].Path); } } @@ -227,24 +248,25 @@ public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorC public void StagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { string fullPath = Touch(scd.RootedDirectoryPath, "unit_test.txt", "some contents"); - Assert.Throws(() => repo.Index.Stage(fullPath)); + Assert.Throws(() => Commands.Stage(repo, fullPath)); } } [Fact] public void StagingFileWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Index.Stage(string.Empty)); - Assert.Throws(() => repo.Index.Stage((string)null)); - Assert.Throws(() => repo.Index.Stage(new string[] { })); - Assert.Throws(() => repo.Index.Stage(new string[] { null })); + Assert.Throws(() => Commands.Stage(repo, string.Empty)); + Assert.Throws(() => Commands.Stage(repo, (string)null)); + Assert.Throws(() => Commands.Stage(repo, Array.Empty())); + Assert.Throws(() => Commands.Stage(repo, new string[] { null })); } } @@ -276,11 +298,11 @@ public void StagingFileWithBadParamsThrows() [InlineData("new_*file.txt", 1)] public void CanStageWithPathspec(string relativePath, int expectedIndexCountVariation) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { int count = repo.Index.Count; - repo.Index.Stage(relativePath); + Commands.Stage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); } @@ -289,11 +311,11 @@ public void CanStageWithPathspec(string relativePath, int expectedIndexCountVari [Fact] public void CanStageWithMultiplePathspecs() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { int count = repo.Index.Count; - repo.Index.Stage(new string[] { "*", "u*" }); + Commands.Stage(repo, new string[] { "*", "u*" }); Assert.Equal(count, repo.Index.Count); // 1 added file, 1 deleted file, so same count } @@ -304,14 +326,14 @@ public void CanStageWithMultiplePathspecs() [InlineData("ignored_folder/file.txt")] public void CanIgnoreIgnoredPaths(string path) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { Touch(repo.Info.WorkingDirectory, ".gitignore", "ignored_file.txt\nignored_folder/\n"); Touch(repo.Info.WorkingDirectory, path, "This file is ignored."); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(path)); - repo.Index.Stage("*"); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(path)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); + Commands.Stage(repo, "*"); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); } } @@ -320,14 +342,71 @@ public void CanIgnoreIgnoredPaths(string path) [InlineData("ignored_folder/file.txt")] public void CanStageIgnoredPaths(string path) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { Touch(repo.Info.WorkingDirectory, ".gitignore", "ignored_file.txt\nignored_folder/\n"); Touch(repo.Info.WorkingDirectory, path, "This file is ignored."); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(path)); - repo.Index.Stage(path, new StageOptions { IncludeIgnored = true }); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(path)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); + Commands.Stage(repo, path, new StageOptions { IncludeIgnored = true }); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(path)); + } + } + + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.Ignored)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInIndex)] + public void IgnoredFilesAreOnlyStagedIfTheyreInTheRepo(string filename, FileStatus expected) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, ".gitignore"), + string.Format("{0}\n", filename)); + + Commands.Stage(repo, filename); + Assert.Equal(expected, repo.RetrieveStatus(filename)); + } + } + + [Theory] + [InlineData("ancestor-and-ours.txt", FileStatus.Unaltered)] + [InlineData("ancestor-and-theirs.txt", FileStatus.NewInIndex)] + [InlineData("ancestor-only.txt", FileStatus.Nonexistent)] + [InlineData("conflicts-one.txt", FileStatus.ModifiedInIndex)] + [InlineData("conflicts-two.txt", FileStatus.ModifiedInIndex)] + [InlineData("ours-only.txt", FileStatus.Unaltered)] + [InlineData("ours-and-theirs.txt", FileStatus.ModifiedInIndex)] + [InlineData("theirs-only.txt", FileStatus.NewInIndex)] + public void CanStageConflictedIgnoredFiles(string filename, FileStatus expected) + { + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) + { + File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, ".gitignore"), + string.Format("{0}\n", filename)); + + Commands.Stage(repo, filename); + Assert.Equal(expected, repo.RetrieveStatus(filename)); + } + } + + [Fact] + public void CanSuccessfullyStageTheContentOfAModifiedFileOfTheSameSizeWithinTheSameSecond() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + for (int i = 0; i < 10; i++) + { + Touch(repo.Info.WorkingDirectory, "test.txt", + Guid.NewGuid().ToString()); + + Commands.Stage(repo, "test.txt"); + + repo.Commit("Commit", Constants.Signature, Constants.Signature); + } } } } diff --git a/LibGit2Sharp.Tests/StashFixture.cs b/LibGit2Sharp.Tests/StashFixture.cs index 2bdf4e33c..27a535e8e 100644 --- a/LibGit2Sharp.Tests/StashFixture.cs +++ b/LibGit2Sharp.Tests/StashFixture.cs @@ -12,7 +12,7 @@ public class StashFixture : BaseFixture [Fact] public void CannotAddStashAgainstBareRepository() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; @@ -24,12 +24,12 @@ public void CannotAddStashAgainstBareRepository() [Fact] public void CanAddAndRemoveStash() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; - Assert.True(repo.Index.RetrieveStatus().IsDirty); + Assert.True(repo.RetrieveStatus().IsDirty); Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashModifiers.IncludeUntracked); @@ -45,7 +45,7 @@ public void CanAddAndRemoveStash() var stashRef = repo.Refs["refs/stash"]; Assert.Equal(stash.WorkTree.Sha, stashRef.TargetIdentifier); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Create extra file untrackedFilename = "stash_candidate.txt"; @@ -69,7 +69,7 @@ public void CanAddAndRemoveStash() //Remove one stash repo.Stashes.Remove(0); - Assert.Equal(1, repo.Stashes.Count()); + Assert.Single(repo.Stashes); Stash newTopStash = repo.Stashes.First(); Assert.Equal("stash@{0}", newTopStash.CanonicalName); Assert.Equal(stash.WorkTree.Sha, newTopStash.WorkTree.Sha); @@ -83,7 +83,7 @@ public void CanAddAndRemoveStash() [Fact] public void AddingAStashWithNoMessageGeneratesADefaultOne() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; @@ -102,7 +102,7 @@ public void AddingAStashWithNoMessageGeneratesADefaultOne() [Fact] public void AddStashWithBadParamsShouldThrows() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.Throws(() => repo.Stashes.Add(default(Signature), options: StashModifiers.Default)); @@ -112,7 +112,7 @@ public void AddStashWithBadParamsShouldThrows() [Fact] public void StashingAgainstCleanWorkDirShouldReturnANullStash() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; @@ -129,7 +129,7 @@ public void StashingAgainstCleanWorkDirShouldReturnANullStash() [Fact] public void CanStashWithoutOptions() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; @@ -139,18 +139,18 @@ public void CanStashWithoutOptions() const string staged = "staged_file_path.txt"; Touch(repo.Info.WorkingDirectory, staged, "I'm staged\n"); - repo.Index.Stage(staged); + Commands.Stage(repo, staged); Stash stash = repo.Stashes.Add(stasher, "Stash with default options", StashModifiers.Default); Assert.NotNull(stash); //It should not keep staged files - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(staged)); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(staged)); Assert.NotNull(stash.Index[staged]); //It should leave untracked files untracked - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(untracked)); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(untracked)); Assert.Null(stash.Untracked); } } @@ -158,20 +158,20 @@ public void CanStashWithoutOptions() [Fact] public void CanStashAndKeepIndex() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; const string filename = "staged_file_path.txt"; Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n"); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); Stash stash = repo.Stashes.Add(stasher, "This stash will keep index", StashModifiers.KeepIndex); Assert.NotNull(stash); Assert.NotNull(stash.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); Assert.Null(stash.Untracked); } } @@ -179,14 +179,14 @@ public void CanStashAndKeepIndex() [Fact] public void CanStashIgnoredFiles() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string gitIgnore = ".gitignore"; const string ignoredFilename = "ignored_file.txt"; Touch(repo.Info.WorkingDirectory, gitIgnore, ignoredFilename); - repo.Index.Stage(gitIgnore); + Commands.Stage(repo, gitIgnore); repo.Commit("Modify gitignore", Constants.Signature, Constants.Signature); Touch(repo.Info.WorkingDirectory, ignoredFilename, "I'm ignored\n"); @@ -204,12 +204,162 @@ public void CanStashIgnoredFiles() } } + [Fact] + public void CanStashAndApplyWithOptions() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "staged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n"); + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(0)); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Single(repo.Stashes); + + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply( + 0, + new StashApplyOptions + { + ApplyModifiers = StashApplyModifiers.ReinstateIndex, + })); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Equal(2, repo.Stashes.Count()); + } + } + + [Fact] + public void CanStashAndPop() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + Assert.Empty(repo.Stashes); + + const string filename = "staged_file_path.txt"; + const string contents = "I'm staged"; + Touch(repo.Info.WorkingDirectory, filename, contents); + Commands.Stage(repo, filename); + + repo.Stashes.Add(stasher, "This stash with default options"); + Assert.Single(repo.Stashes); + + Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Pop(0)); + Assert.Empty(repo.Stashes); + + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Equal(contents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename))); + } + } + + [Fact] + public void StashFailsWithUncommittedChangesIntheIndex() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "staged_file_path.txt"; + const string originalContents = "I'm pre-stash."; + const string filename2 = "unstaged_file_path.txt"; + const string newContents = "I'm post-stash."; + + Touch(repo.Info.WorkingDirectory, filename, originalContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, originalContents); + + repo.Stashes.Add(stasher, "This stash with default options"); + + Touch(repo.Info.WorkingDirectory, filename, newContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, newContents); + + Assert.Equal(StashApplyStatus.UncommittedChanges, repo.Stashes.Pop(0, new StashApplyOptions + { + ApplyModifiers = StashApplyModifiers.ReinstateIndex, + })); + Assert.Single(repo.Stashes); + Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename))); + Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename2))); + } + } + + [Fact] + public void StashCallsTheCallback() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + bool called; + + const string filename = "staged_file_path.txt"; + const string filename2 = "unstaged_file_path.txt"; + const string originalContents = "I'm pre-stash."; + + Touch(repo.Info.WorkingDirectory, filename, originalContents); + Commands.Stage(repo, filename); + Touch(repo.Info.WorkingDirectory, filename2, originalContents); + + repo.Stashes.Add(stasher, "This stash with default options"); + + called = false; + repo.Stashes.Apply(0, new StashApplyOptions + { + ProgressHandler = (progress) => { called = true; return true; } + }); + + Assert.True(called); + + repo.Reset(ResetMode.Hard); + + called = false; + repo.Stashes.Pop(0, new StashApplyOptions + { + ProgressHandler = (progress) => { called = true; return true; } + }); + + Assert.True(called); + } + } + + [Fact] + public void StashApplyReportsNotFound() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + + const string filename = "unstaged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, filename, "I'm unstaged\n"); + + repo.Stashes.Add(stasher, "This stash with default options", StashModifiers.IncludeUntracked); + Touch(repo.Info.WorkingDirectory, filename, "I'm another unstaged\n"); + + Assert.Equal(StashApplyStatus.NotFound, repo.Stashes.Pop(1)); + Assert.Throws(() => repo.Stashes.Pop(-1)); + } + } + [Theory] [InlineData(-1)] [InlineData(-42)] public void RemovingStashWithBadParamShouldThrow(int badIndex) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.Throws(() => repo.Stashes.Remove(badIndex)); @@ -219,7 +369,7 @@ public void RemovingStashWithBadParamShouldThrow(int badIndex) [Fact] public void CanGetStashByIndexer() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { var stasher = Constants.Signature; @@ -263,7 +413,8 @@ public void CanGetStashByIndexer() [InlineData(-42)] public void GettingStashWithBadIndexThrows(int badIndex) { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Stashes[badIndex]); } @@ -274,7 +425,8 @@ public void GettingStashWithBadIndexThrows(int badIndex) [InlineData(42)] public void GettingAStashThatDoesNotExistReturnsNull(int bigIndex) { - using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.Null(repo.Stashes[bigIndex]); } diff --git a/LibGit2Sharp.Tests/StatusFixture.cs b/LibGit2Sharp.Tests/StatusFixture.cs index b70ff0ae0..698639aa4 100644 --- a/LibGit2Sharp.Tests/StatusFixture.cs +++ b/LibGit2Sharp.Tests/StatusFixture.cs @@ -13,44 +13,45 @@ public class StatusFixture : BaseFixture [Fact] public void CanRetrieveTheStatusOfAFile() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - FileStatus status = repo.Index.RetrieveStatus("new_tracked_file.txt"); - Assert.Equal(FileStatus.Added, status); + FileStatus status = repo.RetrieveStatus("new_tracked_file.txt"); + Assert.Equal(FileStatus.NewInIndex, status); } } [Theory] - [InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.Untracked)] - [InlineData(StatusShowOption.WorkDirOnly, FileStatus.Untracked)] + [InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.NewInWorkdir)] + [InlineData(StatusShowOption.WorkDirOnly, FileStatus.NewInWorkdir)] [InlineData(StatusShowOption.IndexOnly, FileStatus.Nonexistent)] public void CanLimitStatusToWorkDirOnly(StatusShowOption show, FileStatus expected) { - var clone = CloneStandardTestRepo(); + var clone = SandboxStandardTestRepo(); using (var repo = new Repository(clone)) { Touch(repo.Info.WorkingDirectory, "file.txt", "content"); - RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { Show = show }); + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { Show = show }); Assert.Equal(expected, status["file.txt"].State); } } [Theory] - [InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.Added)] + [InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.NewInIndex)] [InlineData(StatusShowOption.WorkDirOnly, FileStatus.Nonexistent)] - [InlineData(StatusShowOption.IndexOnly, FileStatus.Added)] + [InlineData(StatusShowOption.IndexOnly, FileStatus.NewInIndex)] public void CanLimitStatusToIndexOnly(StatusShowOption show, FileStatus expected) { - var clone = CloneStandardTestRepo(); + var clone = SandboxStandardTestRepo(); using (var repo = new Repository(clone)) { Touch(repo.Info.WorkingDirectory, "file.txt", "content"); - repo.Index.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); - RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { Show = show }); + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { Show = show }); Assert.Equal(expected, status["file.txt"].State); } } @@ -81,40 +82,43 @@ public void CanLimitStatusToIndexOnly(StatusShowOption show, FileStatus expected [InlineData("dir!/file.txt")] public void CanRetrieveTheStatusOfAnUntrackedFile(string filePath) { - var clone = CloneStandardTestRepo(); + var clone = SandboxStandardTestRepo(); using (var repo = new Repository(clone)) { Touch(repo.Info.WorkingDirectory, filePath, "content"); - FileStatus status = repo.Index.RetrieveStatus(filePath); - Assert.Equal(FileStatus.Untracked, status); + FileStatus status = repo.RetrieveStatus(filePath); + Assert.Equal(FileStatus.NewInWorkdir, status); } } [Fact] public void RetrievingTheStatusOfADirectoryThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Throws(() => { FileStatus status = repo.Index.RetrieveStatus("1"); }); + Assert.Throws(() => { repo.RetrieveStatus("1"); }); } } - [Fact] - public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() + [Theory] + [InlineData(false, 0)] + [InlineData(true, 5)] + public void CanRetrieveTheStatusOfTheWholeWorkingDirectory(bool includeUnaltered, int unalteredCount) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { const string file = "modified_staged_file.txt"; - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = includeUnaltered }); - Assert.Equal(FileStatus.Staged, status[file].State); + Assert.Equal(FileStatus.ModifiedInIndex, status[file].State); Assert.NotNull(status); - Assert.Equal(6, status.Count()); + Assert.Equal(6 + unalteredCount, status.Count()); Assert.True(status.IsDirty); Assert.Equal("new_untracked_file.txt", status.Untracked.Select(s => s.FilePath).Single()); @@ -127,13 +131,13 @@ public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, file), "Tclem's favorite commit message: boom"); - Assert.Equal(FileStatus.Staged | FileStatus.Modified, repo.Index.RetrieveStatus(file)); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(file)); - RepositoryStatus status2 = repo.Index.RetrieveStatus(); - Assert.Equal(FileStatus.Staged | FileStatus.Modified, status2[file].State); + RepositoryStatus status2 = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = includeUnaltered }); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, status2[file].State); Assert.NotNull(status2); - Assert.Equal(6, status2.Count()); + Assert.Equal(6 + unalteredCount, status2.Count()); Assert.True(status2.IsDirty); Assert.Equal("new_untracked_file.txt", status2.Untracked.Select(s => s.FilePath).Single()); @@ -148,7 +152,7 @@ public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() [Fact] public void CanRetrieveTheStatusOfRenamedFilesInWorkDir() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, "old_name.txt", @@ -157,19 +161,19 @@ public void CanRetrieveTheStatusOfRenamedFilesInWorkDir() "This is a file with enough data to trigger similarity matching.\r\n" + "This is a file with enough data to trigger similarity matching.\r\n"); - repo.Index.Stage("old_name.txt"); + Commands.Stage(repo, "old_name.txt"); File.Move(Path.Combine(repo.Info.WorkingDirectory, "old_name.txt"), Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt")); - RepositoryStatus status = repo.Index.RetrieveStatus( + RepositoryStatus status = repo.RetrieveStatus( new StatusOptions() { DetectRenamesInIndex = true, DetectRenamesInWorkDir = true }); - Assert.Equal(FileStatus.Added | FileStatus.RenamedInWorkDir, status["rename_target.txt"].State); + Assert.Equal(FileStatus.NewInIndex | FileStatus.RenamedInWorkdir, status["rename_target.txt"].State); Assert.Equal(100, status["rename_target.txt"].IndexToWorkDirRenameDetails.Similarity); } } @@ -177,17 +181,17 @@ public void CanRetrieveTheStatusOfRenamedFilesInWorkDir() [Fact] public void CanRetrieveTheStatusOfRenamedFilesInIndex() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { File.Move( Path.Combine(repo.Info.WorkingDirectory, "1.txt"), Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt")); - repo.Index.Stage("1.txt"); - repo.Index.Stage("rename_target.txt"); + Commands.Stage(repo, "1.txt"); + Commands.Stage(repo, "rename_target.txt"); - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(); Assert.Equal(FileStatus.RenamedInIndex, status["rename_target.txt"].State); Assert.Equal(100, status["rename_target.txt"].HeadToIndexRenameDetails.Similarity); @@ -206,7 +210,7 @@ public void CanDetectedVariousKindsOfRenaming() "This is a file with enough data to trigger similarity matching.\r\n" + "This is a file with enough data to trigger similarity matching.\r\n"); - repo.Index.Stage("file.txt"); + Commands.Stage(repo, "file.txt"); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); File.Move(Path.Combine(repo.Info.WorkingDirectory, "file.txt"), @@ -218,51 +222,53 @@ public void CanDetectedVariousKindsOfRenaming() DetectRenamesInWorkDir = true }; - RepositoryStatus status = repo.Index.RetrieveStatus(opts); + RepositoryStatus status = repo.RetrieveStatus(opts); // This passes as expected - Assert.Equal(FileStatus.RenamedInWorkDir, status.Single().State); + Assert.Equal(FileStatus.RenamedInWorkdir, status.Single().State); - repo.Index.Stage("file.txt"); - repo.Index.Stage("renamed.txt"); + Commands.Stage(repo, "file.txt"); + Commands.Stage(repo, "renamed.txt"); - status = repo.Index.RetrieveStatus(opts); + status = repo.RetrieveStatus(opts); Assert.Equal(FileStatus.RenamedInIndex, status.Single().State); File.Move(Path.Combine(repo.Info.WorkingDirectory, "renamed.txt"), Path.Combine(repo.Info.WorkingDirectory, "renamed_again.txt")); - status = repo.Index.RetrieveStatus(opts); + status = repo.RetrieveStatus(opts); - Assert.Equal(FileStatus.RenamedInWorkDir | FileStatus.RenamedInIndex, + Assert.Equal(FileStatus.RenamedInWorkdir | FileStatus.RenamedInIndex, status.Single().State); } } - [Fact] - public void CanRetrieveTheStatusOfANewRepository() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanRetrieveTheStatusOfANewRepository(bool includeUnaltered) { string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = includeUnaltered }); Assert.NotNull(status); - Assert.Equal(0, status.Count()); + Assert.Empty(status); Assert.False(status.IsDirty); - Assert.Equal(0, status.Untracked.Count()); - Assert.Equal(0, status.Modified.Count()); - Assert.Equal(0, status.Missing.Count()); - Assert.Equal(0, status.Added.Count()); - Assert.Equal(0, status.Staged.Count()); - Assert.Equal(0, status.Removed.Count()); + Assert.Empty(status.Untracked); + Assert.Empty(status.Modified); + Assert.Empty(status.Missing); + Assert.Empty(status.Added); + Assert.Empty(status.Staged); + Assert.Empty(status.Removed); } } [Fact] - public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() + public void RetrievingTheStatusOfARepositoryReturnsGitPaths() { // Build relative path string relFilePath = Path.Combine("directory", "Testfile.txt"); @@ -275,15 +281,15 @@ public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); // Add the file to the index - repo.Index.Stage(relFilePath); + Commands.Stage(repo, relFilePath); // Get the repository status - RepositoryStatus repoStatus = repo.Index.RetrieveStatus(); + RepositoryStatus repoStatus = repo.RetrieveStatus(); - Assert.Equal(1, repoStatus.Count()); + Assert.Single(repoStatus); StatusEntry statusEntry = repoStatus.Single(); - Assert.Equal(relFilePath, statusEntry.FilePath); + Assert.Equal(relFilePath.Replace('\\', '/'), statusEntry.FilePath); Assert.Equal(statusEntry.FilePath, repoStatus.Added.Select(s => s.FilePath).Single()); } @@ -299,23 +305,46 @@ public void RetrievingTheStatusOfAnEmptyRepositoryHonorsTheGitIgnoreDirectives() const string relativePath = "look-ma.txt"; Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!"); - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(); Assert.Equal(new[] { relativePath }, status.Untracked.Select(s => s.FilePath)); Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); Assert.Equal(new[] { relativePath }, newStatus.Ignored.Select(s => s.FilePath)); } } + [Fact] + public void RetrievingTheStatusWithoutIncludeIgnoredIgnoresButDoesntInclude() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + const string relativePath = "look-ma.txt"; + Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!"); + var opt = new StatusOptions { IncludeIgnored = false }; + Assert.False(opt.IncludeIgnored); + RepositoryStatus status = repo.RetrieveStatus(opt); + Assert.Equal(new[] { relativePath }, status.Untracked.Select(s => s.FilePath)); + + Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); + + RepositoryStatus newStatus = repo.RetrieveStatus(opt); + Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); + + Assert.False(newStatus.Ignored.Any()); + } + } + [Fact] public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { string relativePath = Path.Combine("1", "look-ma.txt"); @@ -352,8 +381,9 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() * # new_untracked_file.txt */ - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(); + relativePath = relativePath.Replace('\\', '/'); Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, status.Untracked.Select(s => s.FilePath)); Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); @@ -393,40 +423,25 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() * # new_untracked_file.txt */ - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored.Select(s => s.FilePath)); } } - [Fact] - public void RetrievingTheStatusOfAnAmbiguousFileThrows() - { - string path = CloneStandardTestRepo(); - using (var repo = new Repository(path)) - { - Touch(repo.Info.WorkingDirectory, "1/ambiguous1.txt", "I don't like brackets."); - - string relativePath = Path.Combine("1", "ambiguous[1].txt"); - Touch(repo.Info.WorkingDirectory, relativePath, "Brackets all the way."); - - Assert.Throws(() => repo.Index.RetrieveStatus(relativePath)); - } - } - [Theory] [InlineData(true, FileStatus.Unaltered, FileStatus.Unaltered)] - [InlineData(false, FileStatus.Missing, FileStatus.Untracked)] + [InlineData(false, FileStatus.DeletedFromWorkdir, FileStatus.NewInWorkdir)] public void RetrievingTheStatusOfAFilePathHonorsTheIgnoreCaseConfigurationSetting( bool shouldIgnoreCase, - FileStatus expectedlowerCasedFileStatus, - FileStatus expectedCamelCasedFileStatus + FileStatus expectedLowercaseFileStatus, + FileStatus expectedUppercaseFileStatus ) { - string lowerCasedPath; - const string lowercasedFilename = "plop"; + string lowercasePath; + const string lowercaseFileName = "plop"; string repoPath = InitNewRepository(); @@ -434,24 +449,28 @@ FileStatus expectedCamelCasedFileStatus { repo.Config.Set("core.ignorecase", shouldIgnoreCase); - lowerCasedPath = Touch(repo.Info.WorkingDirectory, lowercasedFilename); + lowercasePath = Touch(repo.Info.WorkingDirectory, lowercaseFileName); - repo.Index.Stage(lowercasedFilename); + Commands.Stage(repo, lowercaseFileName); repo.Commit("initial", Constants.Signature, Constants.Signature); } using (var repo = new Repository(repoPath)) { - const string upercasedFilename = "Plop"; + const string uppercaseFileName = "PLOP"; - string camelCasedPath = Path.Combine(repo.Info.WorkingDirectory, upercasedFilename); - File.Move(lowerCasedPath, camelCasedPath); + string uppercasePath = Path.Combine(repo.Info.WorkingDirectory, uppercaseFileName); - Assert.Equal(expectedlowerCasedFileStatus, repo.Index.RetrieveStatus(lowercasedFilename)); - Assert.Equal(expectedCamelCasedFileStatus, repo.Index.RetrieveStatus(upercasedFilename)); + //Workaround for problem with .NET Core 1.x on macOS where going directly from lowercasePath to uppercasePath fails + //https://github.com/dotnet/corefx/issues/18521 + File.Move(lowercasePath, "__tmp__"); + File.Move("__tmp__", uppercasePath); - AssertStatus(shouldIgnoreCase, expectedlowerCasedFileStatus, repo, camelCasedPath.ToLowerInvariant()); - AssertStatus(shouldIgnoreCase, expectedCamelCasedFileStatus, repo, camelCasedPath.ToUpperInvariant()); + Assert.Equal(expectedLowercaseFileStatus, repo.RetrieveStatus(lowercaseFileName)); + Assert.Equal(expectedUppercaseFileStatus, repo.RetrieveStatus(uppercaseFileName)); + + AssertStatus(shouldIgnoreCase, expectedLowercaseFileStatus, repo, uppercasePath.ToLowerInvariant()); + AssertStatus(shouldIgnoreCase, expectedUppercaseFileStatus, repo, uppercasePath.ToUpperInvariant()); } } @@ -459,7 +478,7 @@ private static void AssertStatus(bool shouldIgnoreCase, FileStatus expectedFileS { try { - Assert.Equal(expectedFileStatus, repo.Index.RetrieveStatus(path)); + Assert.Equal(expectedFileStatus, repo.RetrieveStatus(path)); } catch (ArgumentException) { @@ -470,9 +489,7 @@ private static void AssertStatus(bool shouldIgnoreCase, FileStatus expectedFileS [Fact] public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroughoutDirectories() { - char dirSep = Path.DirectorySeparatorChar; - - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, "bin/look-ma.txt", "I'm going to be ignored!"); @@ -481,31 +498,31 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroug const string gitIgnore = ".gitignore"; Touch(repo.Info.WorkingDirectory, gitIgnore, "bin"); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus("bin/look-ma.txt")); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus("bin/what-about-me.txt")); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/look-ma.txt")); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/what-about-me.txt")); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(new[] { "bin" + dirSep }, newStatus.Ignored.Select(s => s.FilePath)); + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.Equal(new[] { "bin/" }, newStatus.Ignored.Select(s => s.FilePath)); var sb = new StringBuilder(); sb.AppendLine("bin/*"); sb.AppendLine("!bin/w*"); Touch(repo.Info.WorkingDirectory, gitIgnore, sb.ToString()); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus("bin/look-ma.txt")); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus("bin/what-about-me.txt")); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/look-ma.txt")); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("bin/what-about-me.txt")); - newStatus = repo.Index.RetrieveStatus(); + newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); - Assert.Equal(new[] { "bin" + dirSep + "look-ma.txt" }, newStatus.Ignored.Select(s => s.FilePath)); - Assert.True(newStatus.Untracked.Select(s => s.FilePath).Contains("bin" + dirSep + "what-about-me.txt")); + Assert.Equal(new[] { "bin/look-ma.txt" }, newStatus.Ignored.Select(s => s.FilePath)); + Assert.Contains("bin/what-about-me.txt", newStatus.Untracked.Select(s => s.FilePath)); } } [Fact] public void CanRetrieveStatusOfFilesInSubmodule() { - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { string[] expected = new string[] { @@ -517,7 +534,7 @@ public void CanRetrieveStatusOfFilesInSubmodule() "sm_missing_commits" }; - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(); Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray()); } } @@ -525,16 +542,123 @@ public void CanRetrieveStatusOfFilesInSubmodule() [Fact] public void CanExcludeStatusOfFilesInSubmodule() { - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { string[] expected = new string[] { ".gitmodules", }; - RepositoryStatus status = repo.Index.RetrieveStatus(new StatusOptions() { ExcludeSubmodules = true }); + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { ExcludeSubmodules = true }); Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray()); } } + + [Fact] + public void CanRetrieveTheStatusOfARelativeWorkingDirectory() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + const string file = "just_a_dir/other.txt"; + const string otherFile = "just_a_dir/another_dir/other.txt"; + + Touch(repo.Info.WorkingDirectory, file); + Touch(repo.Info.WorkingDirectory, otherFile); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { PathSpec = new[] { "just_a_dir" } }); + Assert.Equal(2, status.Count()); + Assert.Equal(2, status.Untracked.Count()); + + status = repo.RetrieveStatus(new StatusOptions() { PathSpec = new[] { "just_a_dir/another_dir" } }); + Assert.Single(status); + Assert.Single(status.Untracked); + } + } + + [Fact] + public void CanRetrieveTheStatusOfMultiplePathSpec() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + const string file = "just_a_dir/other.txt"; + const string otherFile = "just_a_file.txt"; + + Touch(repo.Info.WorkingDirectory, file); + Touch(repo.Info.WorkingDirectory, otherFile); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { PathSpec = new[] { "just_a_file.txt", "just_a_dir" } }); + Assert.Equal(2, status.Count()); + Assert.Equal(2, status.Untracked.Count()); + } + } + + [Fact] + public void CanRetrieveTheStatusOfAGlobSpec() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + const string file = "just_a_dir/other.txt"; + const string otherFile = "just_a_file.txt"; + + Touch(repo.Info.WorkingDirectory, file); + Touch(repo.Info.WorkingDirectory, otherFile); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { PathSpec = new[] { "just_a_*" } }); + Assert.Equal(2, status.Count()); + Assert.Equal(2, status.Untracked.Count()); + } + } + + [Fact] + public void RetrievingTheStatusHonorsAssumedUnchangedMarkedIndexEntries() + { + var path = SandboxAssumeUnchangedTestRepo(); + using (var repo = new Repository(path)) + { + var status = repo.RetrieveStatus(); + Assert.Equal("hello.txt", status.Modified.Single().FilePath); + } + } + + [Fact] + public void CanIncludeStatusOfUnalteredFiles() + { + var path = SandboxStandardTestRepo(); + string[] unalteredPaths = { + "1.txt", + "1/branch_file.txt", + "branch_file.txt", + "new.txt", + "README", + }; + + using (var repo = new Repository(path)) + { + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = true }); + + Assert.Equal(unalteredPaths.Length, status.Unaltered.Count()); + Assert.Equal(unalteredPaths, status.Unaltered.OrderBy(s => s.FilePath, StringComparer.OrdinalIgnoreCase).Select(s => s.FilePath).ToArray()); + } + } + + [Fact] + public void UnalteredFilesDontMarkIndexAsDirty() + { + var path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = true }); + + Assert.False(status.IsDirty); + Assert.Equal(9, status.Count()); + } + } } } diff --git a/LibGit2Sharp.Tests/SubmoduleFixture.cs b/LibGit2Sharp.Tests/SubmoduleFixture.cs index 2463912fd..2d7f04e6d 100644 --- a/LibGit2Sharp.Tests/SubmoduleFixture.cs +++ b/LibGit2Sharp.Tests/SubmoduleFixture.cs @@ -3,7 +3,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,7 +11,7 @@ public class SubmoduleFixture : BaseFixture [Fact] public void RetrievingSubmoduleForNormalDirectoryReturnsNull() { - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodule = repo.Submodules["just_a_dir"]; @@ -20,6 +19,26 @@ public void RetrievingSubmoduleForNormalDirectoryReturnsNull() } } + [Fact] + public void RetrievingSubmoduleInBranchShouldWork() + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules["sm_branch_only"]; + Assert.Null(submodule); + + Commands.Checkout(repo, "dev", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + submodule = repo.Submodules["sm_branch_only"]; + Assert.NotNull(submodule); + Assert.NotEqual(SubmoduleStatus.Unmodified, submodule.RetrieveStatus()); + + Commands.Checkout(repo, "master", new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }); + submodule = repo.Submodules["sm_branch_only"]; + Assert.Null(submodule); + } + } + [Theory] [InlineData("sm_added_and_uncommited", SubmoduleStatus.InConfig | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.IndexAdded)] [InlineData("sm_changed_file", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesModified)] @@ -29,12 +48,20 @@ public void RetrievingSubmoduleForNormalDirectoryReturnsNull() [InlineData("sm_gitmodules_only", SubmoduleStatus.InConfig)] [InlineData("sm_missing_commits", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirModified)] [InlineData("sm_unchanged", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir)] - public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus expectedStatus) + [InlineData("sm_branch_only", null)] + public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus? expectedStatus) { - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodule = repo.Submodules[name]; + + if (expectedStatus == null) + { + Assert.Null(submodule); + return; + } + Assert.NotNull(submodule); Assert.Equal(name, submodule.Name); Assert.Equal(name, submodule.Path); @@ -55,7 +82,7 @@ public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus expect [InlineData("sm_unchanged", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] public void CanRetrieveTheCommitIdsOfASubmodule(string name, string headId, string indexId, string workDirId) { - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodule = repo.Submodules[name]; @@ -92,7 +119,7 @@ public void CanEnumerateRepositorySubmodules() "sm_unchanged", }; - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodules = repo.Submodules.OrderBy(s => s.Name, StringComparer.Ordinal); @@ -105,13 +132,11 @@ public void CanEnumerateRepositorySubmodules() } [Theory] - [InlineData("sm_changed_head", false)] - [InlineData("sm_changed_head", true)] - public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool appendPathSeparator) + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath) { - submodulePath += appendPathSeparator ? Path.DirectorySeparatorChar : default(char?); - - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodule = repo.Submodules[submodulePath]; @@ -120,7 +145,7 @@ public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool ap var statusBefore = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.WorkDirModified, statusBefore & SubmoduleStatus.WorkDirModified); - repo.Index.Stage(submodulePath); + Commands.Stage(repo, submodulePath); var statusAfter = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified); @@ -128,13 +153,11 @@ public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath, bool ap } [Theory] - [InlineData("sm_changed_head", false)] - [InlineData("sm_changed_head", true)] - public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodulePath, bool appendPathSeparator) + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodulePath) { - submodulePath += appendPathSeparator ? Path.DirectorySeparatorChar : default(char?); - - var path = CloneSubmoduleTestRepo(); + var path = SandboxSubmoduleTestRepo(); using (var repo = new Repository(path)) { var submodule = repo.Submodules[submodulePath]; @@ -145,11 +168,170 @@ public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodul Touch(repo.Info.WorkingDirectory, "new-file.txt"); - repo.Index.Stage(new[] { "new-file.txt", submodulePath, "does-not-exist.txt" }); + Commands.Stage(repo, new[] { "new-file.txt", submodulePath, "does-not-exist.txt" }); var statusAfter = submodule.RetrieveStatus(); Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified); } } + + [Fact] + public void CanInitSubmodule() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + string expectedSubmodulePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path), submoduleName)); + string expectedSubmoduleUrl = expectedSubmodulePath.Replace('\\', '/'); + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.NotNull(submodule); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirUninitialized)); + + var configEntryBeforeInit = repo.Config.Get(string.Format("submodule.{0}.url", submoduleName)); + Assert.Null(configEntryBeforeInit); + + repo.Submodules.Init(submodule.Name, false); + + var configEntryAfterInit = repo.Config.Get(string.Format("submodule.{0}.url", submoduleName)); + Assert.NotNull(configEntryAfterInit); + Assert.Equal(expectedSubmoduleUrl, configEntryAfterInit.Value); + } + } + + [Fact] + public void UpdatingUninitializedSubmoduleThrows() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.NotNull(submodule); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirUninitialized)); + + Assert.Throws(() => repo.Submodules.Update(submodule.Name, new SubmoduleUpdateOptions())); + } + } + + [Fact] + public void CanUpdateSubmodule() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.NotNull(submodule); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirUninitialized)); + + bool checkoutProgressCalled = false; + bool checkoutNotifyCalled = false; + bool updateTipsCalled = false; + var options = new SubmoduleUpdateOptions() + { + OnCheckoutProgress = (x, y, z) => checkoutProgressCalled = true, + OnCheckoutNotify = (x, y) => { checkoutNotifyCalled = true; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + options.FetchOptions.OnUpdateTips = (x, y, z) => { updateTipsCalled = true; return true; }; + + repo.Submodules.Init(submodule.Name, false); + repo.Submodules.Update(submodule.Name, options); + + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir)); + Assert.True(checkoutProgressCalled); + Assert.True(checkoutNotifyCalled); + Assert.True(updateTipsCalled); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.HeadCommitId); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.IndexCommitId); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.WorkDirCommitId); + } + } + + [Fact] + public void CanInitializeAndUpdateSubmodule() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.NotNull(submodule); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirUninitialized)); + + repo.Submodules.Update(submodule.Name, new SubmoduleUpdateOptions() { Init = true }); + + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir)); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.HeadCommitId); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.IndexCommitId); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.WorkDirCommitId); + } + } + + [Fact] + public void CanUpdateSubmoduleAfterCheckout() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.NotNull(submodule); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirUninitialized)); + + repo.Submodules.Init(submodule.Name, false); + repo.Submodules.Update(submodule.Name, new SubmoduleUpdateOptions()); + + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.InWorkDir)); + + Commands.Checkout(repo, "alternate"); + Assert.True(submodule.RetrieveStatus().HasFlag(SubmoduleStatus.WorkDirModified)); + + submodule = repo.Submodules[submoduleName]; + + Assert.Equal((ObjectId)"5e4963595a9774b90524d35a807169049de8ccad", submodule.HeadCommitId); + Assert.Equal((ObjectId)"5e4963595a9774b90524d35a807169049de8ccad", submodule.IndexCommitId); + Assert.Equal((ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0", submodule.WorkDirCommitId); + + repo.Submodules.Update(submodule.Name, new SubmoduleUpdateOptions()); + submodule = repo.Submodules[submoduleName]; + + Assert.Equal((ObjectId)"5e4963595a9774b90524d35a807169049de8ccad", submodule.HeadCommitId); + Assert.Equal((ObjectId)"5e4963595a9774b90524d35a807169049de8ccad", submodule.IndexCommitId); + Assert.Equal((ObjectId)"5e4963595a9774b90524d35a807169049de8ccad", submodule.WorkDirCommitId); + } + } + + [Fact] + public void CanReadSubmoduleProperties() + { + var path = SandboxSubmoduleSmallTestRepo(); + string submoduleName = "submodule_target_wd"; + + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submoduleName]; + + Assert.Equal(SubmoduleUpdate.Checkout, submodule.UpdateRule); + Assert.Equal(SubmoduleIgnore.None, submodule.IgnoreRule); + + // Libgit2 currently returns No by default, which seems incorrect - + // I would expect OnDemand. For now, just test that we can query + // lg2 for this property. + Assert.Equal(SubmoduleRecurse.No, submodule.FetchRecurseSubmodulesRule); + } + } } } diff --git a/LibGit2Sharp.Tests/TagFixture.cs b/LibGit2Sharp.Tests/TagFixture.cs index bd008af34..9f125806c 100644 --- a/LibGit2Sharp.Tests/TagFixture.cs +++ b/LibGit2Sharp.Tests/TagFixture.cs @@ -4,6 +4,7 @@ using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,7 +13,7 @@ public class TagFixture : BaseFixture private readonly string[] expectedTags = new[] { "e90810b", "lw", "point_to_blob", "tag_without_tagger", "test", }; private static readonly Signature signatureTim = new Signature("Tim Clem", "timothy.clem@gmail.com", TruncateSubSeconds(DateTimeOffset.UtcNow)); - private static readonly Signature signatureNtk = new Signature("nulltoken", "emeric.fermas@gmail.com", Epoch.ToDateTimeOffset(1300557894, 60)); + private static readonly Signature signatureNtk = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.FromUnixTimeSeconds(1300557894).ToOffset(TimeSpan.FromMinutes(60))); private const string tagTestSha = "b25fa35b38051e4ae45d4222e795f9df2e43f1d1"; private const string commitE90810BSha = "e90810b8df3e80c413d903f631643c716887138d"; private const string tagE90810BSha = "7b4384978d2493e851f9cca7858815fac9b10980"; @@ -20,7 +21,7 @@ public class TagFixture : BaseFixture [Fact] public void CanAddALightWeightTagFromSha() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", commitE90810BSha); @@ -33,7 +34,7 @@ public void CanAddALightWeightTagFromSha() [Fact] public void CanAddALightWeightTagFromAGitObject() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { GitObject obj = repo.Lookup(commitE90810BSha); @@ -48,7 +49,7 @@ public void CanAddALightWeightTagFromAGitObject() [Fact] public void CanAddALightWeightTagFromAbbreviatedSha() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", commitE90810BSha.Substring(0, 17)); @@ -60,7 +61,7 @@ public void CanAddALightWeightTagFromAbbreviatedSha() [Fact] public void CanAddALightweightTagFromABranchName() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", "refs/heads/master"); @@ -72,7 +73,7 @@ public void CanAddALightweightTagFromABranchName() [Fact] public void CanAddALightweightTagFromARevparseSpec() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", "master^1^2"); @@ -85,7 +86,7 @@ public void CanAddALightweightTagFromARevparseSpec() [Fact] public void CanAddAndOverwriteALightweightTag() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("e90810b", commitE90810BSha, true); @@ -97,7 +98,7 @@ public void CanAddAndOverwriteALightweightTag() [Fact] public void CanAddATagWithNameContainingASlash() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string lwTagName = "i/am/deep"; @@ -105,7 +106,7 @@ public void CanAddATagWithNameContainingASlash() Assert.NotNull(lwTag); Assert.False(lwTag.IsAnnotated); Assert.Equal(commitE90810BSha, lwTag.Target.Sha); - Assert.Equal(lwTagName, lwTag.Name); + Assert.Equal(lwTagName, lwTag.FriendlyName); const string anTagName = lwTagName + "_as_well"; Tag anTag = repo.Tags.Add(anTagName, commitE90810BSha, signatureNtk, "a nice message"); @@ -113,14 +114,14 @@ public void CanAddATagWithNameContainingASlash() Assert.True(anTag.IsAnnotated); Assert.Equal(commitE90810BSha, anTag.Target.Sha); Assert.Equal(anTag.Target, anTag.Annotation.Target); - Assert.Equal(anTagName, anTag.Name); + Assert.Equal(anTagName, anTag.FriendlyName); } } [Fact] public void CreatingATagWithNameMatchingAnAlreadyExistingReferenceHierarchyThrows() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.ApplyTag("i/am/deep"); @@ -132,7 +133,7 @@ public void CreatingATagWithNameMatchingAnAlreadyExistingReferenceHierarchyThrow [Fact] public void CanAddAnAnnotatedTagFromABranchName() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", "refs/heads/master", signatureTim, "a new tag"); @@ -144,7 +145,7 @@ public void CanAddAnAnnotatedTagFromABranchName() [Fact] public void CanAddAnAnnotatedTagFromSha() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", tagTestSha, signatureTim, "a new tag"); @@ -158,12 +159,12 @@ public void CanAddAnAnnotatedTagFromSha() [Fact] public void CanAddAnAnnotatedTagFromObject() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { GitObject obj = repo.Lookup(tagTestSha); - Tag newTag = repo.Tags.Add("unit_test",obj, signatureTim, "a new tag"); + Tag newTag = repo.Tags.Add("unit_test", obj, signatureTim, "a new tag"); Assert.NotNull(newTag); Assert.True(newTag.IsAnnotated); Assert.Equal(tagTestSha, newTag.Target.Sha); @@ -173,7 +174,7 @@ public void CanAddAnAnnotatedTagFromObject() [Fact] public void CanAddAnAnnotatedTagFromARevparseSpec() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", "master^1^2", signatureTim, "a new tag"); @@ -187,7 +188,7 @@ public void CanAddAnAnnotatedTagFromARevparseSpec() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L359) public void CanAddAnAnnotatedTagWithAnEmptyMessage() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.ApplyTag("empty-annotated-tag", signatureNtk, string.Empty); @@ -200,7 +201,7 @@ public void CanAddAnAnnotatedTagWithAnEmptyMessage() [Fact] public void CanAddAndOverwriteAnAnnotatedTag() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("e90810b", tagTestSha, signatureTim, "a new tag", true); @@ -215,7 +216,7 @@ public void CreatingAnAnnotatedTagIsDeterministic() const string tagName = "nullTAGen"; const string tagMessage = "I've been tagged!"; - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add(tagName, commitE90810BSha, signatureNtk, tagMessage); @@ -255,9 +256,10 @@ public void CreatingATagForHeadInAEmptyRepositoryThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L42) public void CreatingATagForAnUnknowReferenceThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.ApplyTag("mytagnorev", "aaaaaaaaaaa")); + Assert.Throws(() => repo.ApplyTag("mytagnorev", "aaaaaaaaaaa")); } } @@ -265,9 +267,10 @@ public void CreatingATagForAnUnknowReferenceThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L42) public void CreatingATagForAnUnknowObjectIdThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.ApplyTag("mytagnorev", Constants.UnknownSha)); + Assert.Throws(() => repo.ApplyTag("mytagnorev", Constants.UnknownSha)); } } @@ -275,7 +278,7 @@ public void CreatingATagForAnUnknowObjectIdThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L48) public void CanAddATagForImplicitHead() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag tag = repo.ApplyTag("mytag"); @@ -291,10 +294,10 @@ public void CanAddATagForImplicitHead() [Fact] public void CanAddATagForImplicitHeadInDetachedState() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip); + Commands.Checkout(repo, repo.Head.Tip); Assert.True(repo.Info.IsHeadDetached); @@ -312,7 +315,7 @@ public void CanAddATagForImplicitHeadInDetachedState() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L87) public void CreatingADuplicateTagThrows() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.ApplyTag("mytag"); @@ -325,7 +328,8 @@ public void CreatingADuplicateTagThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L90) public void CreatingATagWithANonValidNameShouldFail() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.ApplyTag("")); Assert.Throws(() => repo.ApplyTag(".othertag")); @@ -339,7 +343,7 @@ public void CreatingATagWithANonValidNameShouldFail() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L101) public void CanAddATagUsingHead() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag tag = repo.ApplyTag("mytag", "HEAD"); @@ -355,7 +359,7 @@ public void CanAddATagUsingHead() [Fact] public void CanAddATagPointingToATree() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Commit headCommit = repo.Head.Tip; @@ -367,7 +371,7 @@ public void CanAddATagPointingToATree() Assert.Equal(tree.Id, tag.Target.Id); Assert.Equal(tree, repo.Lookup(tag.Target.Id)); - Assert.Equal(tag, repo.Tags[tag.Name]); + Assert.Equal(tag, repo.Tags[tag.FriendlyName]); } } @@ -375,7 +379,8 @@ public void CanAddATagPointingToATree() public void CanReadTagWithoutTagger() { // Not all tags have a tagger. - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["tag_without_tagger"]; @@ -393,7 +398,7 @@ public void CanReadTagWithoutTagger() [Fact] public void CanAddATagPointingToABlob() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { var blob = repo.Lookup("a823312"); @@ -404,14 +409,14 @@ public void CanAddATagPointingToABlob() Assert.Equal(blob.Id, tag.Target.Id); Assert.Equal(blob, repo.Lookup(tag.Target.Id)); - Assert.Equal(tag, repo.Tags[tag.Name]); + Assert.Equal(tag, repo.Tags[tag.FriendlyName]); } } [Fact] public void CreatingALightweightTagPointingToATagAnnotationGeneratesAnAnnotatedTagReusingThePointedAtTagAnnotation() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag annotatedTag = repo.Tags["e90810b"]; @@ -424,14 +429,14 @@ public void CreatingALightweightTagPointingToATagAnnotationGeneratesAnAnnotatedT Assert.Equal(annotation, tag.Annotation); Assert.Equal(annotation, repo.Lookup(tag.Annotation.Id)); - Assert.Equal(tag, repo.Tags[tag.Name]); + Assert.Equal(tag, repo.Tags[tag.FriendlyName]); } } [Fact] public void CanAddAnAnnotatedTagPointingToATagAnnotation() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag annotatedTag = repo.Tags["e90810b"]; @@ -443,14 +448,15 @@ public void CanAddAnAnnotatedTagPointingToATagAnnotation() Assert.Equal(annotation.Id, tag.Annotation.Target.Id); Assert.NotEqual(annotation, tag.Annotation); - Assert.Equal(tag, repo.Tags[tag.Name]); + Assert.Equal(tag, repo.Tags[tag.FriendlyName]); } } [Fact] public void BlindlyCreatingALightweightTagOverAnExistingOneThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("e90810b", "refs/heads/br2")); } @@ -459,7 +465,8 @@ public void BlindlyCreatingALightweightTagOverAnExistingOneThrows() [Fact] public void BlindlyCreatingAnAnnotatedTagOverAnExistingOneThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("e90810b", "refs/heads/br2", signatureNtk, "a nice message")); } @@ -468,7 +475,8 @@ public void BlindlyCreatingAnAnnotatedTagOverAnExistingOneThrows() [Fact] public void AddTagWithADuplicateNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("test", tagTestSha, signatureTim, "message")); } @@ -477,7 +485,8 @@ public void AddTagWithADuplicateNameThrows() [Fact] public void AddTagWithEmptyNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add(string.Empty, "refs/heads/master", signatureTim, "message")); } @@ -486,7 +495,8 @@ public void AddTagWithEmptyNameThrows() [Fact] public void AddTagWithEmptyTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("test_tag", string.Empty, signatureTim, "message")); } @@ -495,16 +505,18 @@ public void AddTagWithEmptyTargetThrows() [Fact] public void AddTagWithNotExistingTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Tags.Add("test_tag", Constants.UnknownSha, signatureTim, "message")); + Assert.Throws(() => repo.Tags.Add("test_tag", Constants.UnknownSha, signatureTim, "message")); } } [Fact] public void AddTagWithNullMessageThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("test_tag", "refs/heads/master", signatureTim, null)); } @@ -513,7 +525,8 @@ public void AddTagWithNullMessageThrows() [Fact] public void AddTagWithNullNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add(null, "refs/heads/master", signatureTim, "message")); } @@ -522,7 +535,8 @@ public void AddTagWithNullNameThrows() [Fact] public void AddTagWithNullSignatureThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("test_tag", "refs/heads/master", null, "message")); } @@ -531,7 +545,8 @@ public void AddTagWithNullSignatureThrows() [Fact] public void AddTagWithNullTargetThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Tags.Add("test_tag", (GitObject)null, signatureTim, "message")); Assert.Throws(() => repo.Tags.Add("test_tag", (string)null, signatureTim, "message")); @@ -541,7 +556,7 @@ public void AddTagWithNullTargetThrows() [Fact] public void CanRemoveATagThroughItsName() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Tags.Remove("e90810b"); @@ -551,7 +566,7 @@ public void CanRemoveATagThroughItsName() [Fact] public void CanRemoveATagThroughItsCanonicalName() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { repo.Tags.Remove("refs/tags/e90810b"); @@ -561,7 +576,7 @@ public void CanRemoveATagThroughItsCanonicalName() [Fact] public void CanRemoveATag() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { Tag tag = repo.Tags["e90810b"]; @@ -572,7 +587,7 @@ public void CanRemoveATag() [Fact] public void ARemovedTagCannotBeLookedUp() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string tagName = "e90810b"; @@ -585,18 +600,18 @@ public void ARemovedTagCannotBeLookedUp() [Fact] public void RemovingATagDecreasesTheTagsCount() { - string path = CloneBareTestRepo(); + string path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { const string tagName = "e90810b"; - List tags = repo.Tags.Select(r => r.Name).ToList(); - Assert.True(tags.Contains(tagName)); + List tags = repo.Tags.Select(r => r.FriendlyName).ToList(); + Assert.Contains(tagName, tags); repo.Tags.Remove(tagName); - List tags2 = repo.Tags.Select(r => r.Name).ToList(); - Assert.False(tags2.Contains(tagName)); + List tags2 = repo.Tags.Select(r => r.FriendlyName).ToList(); + Assert.DoesNotContain(tagName, tags2); Assert.Equal(tags.Count - 1, tags2.Count); } @@ -606,16 +621,18 @@ public void RemovingATagDecreasesTheTagsCount() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L108) public void RemovingAnUnknownTagShouldFail() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Tags.Remove("unknown-tag")); + Assert.Throws(() => repo.Tags.Remove("unknown-tag")); } } [Fact] public void GetTagByNameWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag; Assert.Throws(() => tag = repo.Tags[null]); @@ -626,9 +643,10 @@ public void GetTagByNameWithBadParamsThrows() [Fact] public void CanListTags() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(expectedTags, SortedTags(repo.Tags, t => t.Name)); + Assert.Equal(expectedTags, SortedTags(repo.Tags, t => t.FriendlyName)); Assert.Equal(5, repo.Tags.Count()); } @@ -643,18 +661,19 @@ public void CanListAllTagsInAEmptyRepository() using (var repo = new Repository(repoPath)) { Assert.True(repo.Info.IsHeadUnborn); - Assert.Equal(0, repo.Tags.Count()); + Assert.Empty(repo.Tags); } } [Fact] public void CanLookupALightweightTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["lw"]; Assert.NotNull(tag); - Assert.Equal("lw", tag.Name); + Assert.Equal("lw", tag.FriendlyName); Assert.Equal(commitE90810BSha, tag.Target.Sha); Assert.False(tag.IsAnnotated); @@ -665,15 +684,16 @@ public void CanLookupALightweightTag() [Fact] public void CanLookupATagByItsCanonicalName() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["refs/tags/lw"]; Assert.NotNull(tag); - Assert.Equal("lw", tag.Name); + Assert.Equal("lw", tag.FriendlyName); Tag tag2 = repo.Tags["refs/tags/lw"]; Assert.NotNull(tag2); - Assert.Equal("lw", tag2.Name); + Assert.Equal("lw", tag2.FriendlyName); Assert.Equal(tag, tag2); Assert.True((tag2 == tag)); @@ -683,11 +703,12 @@ public void CanLookupATagByItsCanonicalName() [Fact] public void CanLookupAnAnnotatedTag() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["e90810b"]; Assert.NotNull(tag); - Assert.Equal("e90810b", tag.Name); + Assert.Equal("e90810b", tag.FriendlyName); Assert.Equal(commitE90810BSha, tag.Target.Sha); Assert.True(tag.IsAnnotated); @@ -703,7 +724,8 @@ public void CanLookupAnAnnotatedTag() [Fact] public void LookupEmptyTagNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => { Tag t = repo.Tags[string.Empty]; }); } @@ -712,12 +734,42 @@ public void LookupEmptyTagNameThrows() [Fact] public void LookupNullTagNameThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => { Tag t = repo.Tags[null]; }); } } + [Fact] + public void CanRetrieveThePeeledTargetOfATagPointingToATag() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Tag tag = repo.Tags["test"]; + + Assert.True(tag.Target is TagAnnotation); + Assert.True(tag.PeeledTarget is Commit); + } + } + + [Theory] + [InlineData("e90810b")] + [InlineData("lw")] + [InlineData("point_to_blob")] + [InlineData("tag_without_tagger")] + public void PeeledTargetAndTargetAreEqualWhenTagIsNotChained(string tagName) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Tag tag = repo.Tags[tagName]; + + Assert.Equal(tag.Target, tag.PeeledTarget); + } + } + private static T[] SortedTags(IEnumerable tags, Func selector) { return tags.OrderBy(t => t.CanonicalName, StringComparer.Ordinal).Select(selector).ToArray(); diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs index 2cab50d5a..e9429d562 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -7,7 +7,6 @@ using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using LibGit2Sharp.Core; using Xunit; namespace LibGit2Sharp.Tests.TestHelpers @@ -16,12 +15,19 @@ public class BaseFixture : IPostTestDirectoryRemover, IDisposable { private readonly List directories = new List(); + public BaseFixture() + { + BuildFakeConfigs(this); + +#if LEAKS_IDENTIFYING + Core.LeaksContainer.Clear(); +#endif + } + static BaseFixture() { // Do the set up in the static ctor so it only happens once SetUpTestEnvironment(); - - DirectoryHelper.DeleteSubdirectories(Constants.TemporaryReposPath); } public static string BareTestRepoPath { get; private set; } @@ -33,46 +39,111 @@ static BaseFixture() public static string MergeRenamesTestRepoWorkingDirPath { get; private set; } public static string RevertTestRepoWorkingDirPath { get; private set; } public static string SubmoduleTestRepoWorkingDirPath { get; private set; } + private static string SubmoduleTargetTestRepoWorkingDirPath { get; set; } + private static string AssumeUnchangedRepoWorkingDirPath { get; set; } + public static string SubmoduleSmallTestRepoWorkingDirPath { get; set; } + public static string WorktreeTestRepoWorkingDirPath { get; private set; } + public static string WorktreeTestRepoWorktreesDirPath { get; private set; } + public static string PackBuilderTestRepoPath { get; private set; } + public static DirectoryInfo ResourcesDirectory { get; private set; } public static bool IsFileSystemCaseSensitive { get; private set; } protected static DateTimeOffset TruncateSubSeconds(DateTimeOffset dto) { - int seconds = dto.ToSecondsSinceEpoch(); - return Epoch.ToDateTimeOffset(seconds, (int) dto.Offset.TotalMinutes); + var seconds = dto.ToUnixTimeSeconds(); + return DateTimeOffset.FromUnixTimeSeconds(seconds).ToOffset(dto.Offset); } private static void SetUpTestEnvironment() { IsFileSystemCaseSensitive = IsFileSystemCaseSensitiveInternal(); - var source = new DirectoryInfo(@"../../Resources"); - ResourcesDirectory = new DirectoryInfo(string.Format(@"Resources/{0}", Guid.NewGuid())); - var parent = new DirectoryInfo(@"Resources"); + var resourcesPath = Environment.GetEnvironmentVariable("LIBGIT2SHARP_RESOURCES"); - if (parent.Exists) + if (resourcesPath == null) { - DirectoryHelper.DeleteSubdirectories(parent.FullName); +#if NETFRAMEWORK + resourcesPath = Path.Combine(Directory.GetParent(new Uri(typeof(BaseFixture).GetTypeInfo().Assembly.CodeBase).LocalPath).FullName, "Resources"); +#else + resourcesPath = Path.Combine(Directory.GetParent(typeof(BaseFixture).GetTypeInfo().Assembly.Location).FullName, "Resources"); +#endif } - DirectoryHelper.CopyFilesRecursively(source, ResourcesDirectory); + ResourcesDirectory = new DirectoryInfo(resourcesPath); // Setup standard paths to our test repositories BareTestRepoPath = Path.Combine(ResourcesDirectory.FullName, "testrepo.git"); StandardTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "testrepo_wd"); - StandardTestRepoPath = Path.Combine(StandardTestRepoWorkingDirPath, ".git"); + StandardTestRepoPath = Path.Combine(StandardTestRepoWorkingDirPath, "dot_git"); ShallowTestRepoPath = Path.Combine(ResourcesDirectory.FullName, "shallow.git"); MergedTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "mergedrepo_wd"); MergeRenamesTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "mergerenames_wd"); MergeTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "merge_testrepo_wd"); RevertTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "revert_testrepo_wd"); SubmoduleTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "submodule_wd"); + SubmoduleTargetTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "submodule_target_wd"); + AssumeUnchangedRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "assume_unchanged_wd"); + SubmoduleSmallTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "submodule_small_wd"); + PackBuilderTestRepoPath = Path.Combine(ResourcesDirectory.FullName, "packbuilder_testrepo_wd"); + WorktreeTestRepoWorkingDirPath = Path.Combine(ResourcesDirectory.FullName, "worktree", "testrepo_wd"); + WorktreeTestRepoWorktreesDirPath = Path.Combine(ResourcesDirectory.FullName, "worktree", "worktrees"); + + CleanupTestReposOlderThan(TimeSpan.FromMinutes(15)); + } + + public static void BuildFakeConfigs(IPostTestDirectoryRemover dirRemover) + { + var scd = new SelfCleaningDirectory(dirRemover); + + string global = null, xdg = null, system = null, programData = null; + BuildFakeRepositoryOptions(scd, out global, out xdg, out system, out programData); + + StringBuilder sb = new StringBuilder() + .AppendFormat("[Woot]{0}", Environment.NewLine) + .AppendFormat("this-rocks = global{0}", Environment.NewLine) + .AppendFormat("[Wow]{0}", Environment.NewLine) + .AppendFormat("Man-I-am-totally-global = 42{0}", Environment.NewLine); + File.WriteAllText(Path.Combine(global, ".gitconfig"), sb.ToString()); + + sb = new StringBuilder() + .AppendFormat("[Woot]{0}", Environment.NewLine) + .AppendFormat("this-rocks = system{0}", Environment.NewLine); + File.WriteAllText(Path.Combine(system, "gitconfig"), sb.ToString()); + + sb = new StringBuilder() + .AppendFormat("[Woot]{0}", Environment.NewLine) + .AppendFormat("this-rocks = xdg{0}", Environment.NewLine); + File.WriteAllText(Path.Combine(xdg, "config"), sb.ToString()); + + sb = new StringBuilder() + .AppendFormat("[Woot]{0}", Environment.NewLine) + .AppendFormat("this-rocks = programdata{0}", Environment.NewLine); + File.WriteAllText(Path.Combine(programData, "config"), sb.ToString()); + + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Global, global); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.Xdg, xdg); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.System, system); + GlobalSettings.SetConfigSearchPaths(ConfigurationLevel.ProgramData, programData); + } + + private static void CleanupTestReposOlderThan(TimeSpan olderThan) + { + var oldTestRepos = new DirectoryInfo(Constants.TemporaryReposPath) + .EnumerateDirectories() + .Where(di => di.CreationTimeUtc < DateTimeOffset.Now.Subtract(olderThan)) + .Select(di => di.FullName); + + foreach (var dir in oldTestRepos) + { + DirectoryHelper.DeleteDirectory(dir); + } } private static bool IsFileSystemCaseSensitiveInternal() { - var mixedPath = Path.Combine(Constants.TemporaryReposPath, "mIxEdCase"); + var mixedPath = Path.Combine(Constants.TemporaryReposPath, "mIxEdCase-" + Path.GetRandomFileName()); if (Directory.Exists(mixedPath)) { @@ -87,14 +158,6 @@ private static bool IsFileSystemCaseSensitiveInternal() return !isInsensitive; } - // Should match LibGit2Sharp.Core.NativeMethods.IsRunningOnLinux() - protected static bool IsRunningOnLinux() - { - // see http://mono-project.com/FAQ%3a_Technical#Mono_Platforms - var p = (int)Environment.OSVersion.Platform; - return (p == 4) || (p == 6) || (p == 128); - } - protected void CreateCorruptedDeadBeefHead(string repoPath) { const string deadbeef = "deadbeef"; @@ -113,43 +176,65 @@ protected SelfCleaningDirectory BuildSelfCleaningDirectory(string path) return new SelfCleaningDirectory(this, path); } - protected string CloneBareTestRepo() + protected string SandboxBareTestRepo() { - return Clone(BareTestRepoPath); + return Sandbox(BareTestRepoPath); } - protected string CloneStandardTestRepo() + protected string SandboxStandardTestRepo() { - return Clone(StandardTestRepoWorkingDirPath); + return Sandbox(StandardTestRepoWorkingDirPath); } - protected string CloneMergedTestRepo() + protected string SandboxMergedTestRepo() { - return Clone(MergedTestRepoWorkingDirPath); + return Sandbox(MergedTestRepoWorkingDirPath); } - protected string CloneMergeRenamesTestRepo() + protected string SandboxStandardTestRepoGitDir() { - return Clone(MergeRenamesTestRepoWorkingDirPath); + return Sandbox(Path.Combine(StandardTestRepoWorkingDirPath)); } - protected string CloneMergeTestRepo() + protected string SandboxMergeTestRepo() { - return Clone(MergeTestRepoWorkingDirPath); + return Sandbox(MergeTestRepoWorkingDirPath); + } + + protected string SandboxRevertTestRepo() + { + return Sandbox(RevertTestRepoWorkingDirPath); + } + + public string SandboxSubmoduleTestRepo() + { + return Sandbox(SubmoduleTestRepoWorkingDirPath, SubmoduleTargetTestRepoWorkingDirPath); + } + + public string SandboxAssumeUnchangedTestRepo() + { + return Sandbox(AssumeUnchangedRepoWorkingDirPath); + } + + public string SandboxSubmoduleSmallTestRepo() + { + var path = Sandbox(SubmoduleSmallTestRepoWorkingDirPath, SubmoduleTargetTestRepoWorkingDirPath); + Directory.CreateDirectory(Path.Combine(path, "submodule_target_wd")); + + return path; } - protected string CloneRevertTestRepo() + public string SandboxWorktreeTestRepo() { - return Clone(RevertTestRepoWorkingDirPath); + return Sandbox(WorktreeTestRepoWorkingDirPath, WorktreeTestRepoWorktreesDirPath); } - public string CloneSubmoduleTestRepo() + protected string SandboxPackBuilderTestRepo() { - var submoduleTarget = Path.Combine(ResourcesDirectory.FullName, "submodule_target_wd"); - return Clone(SubmoduleTestRepoWorkingDirPath, submoduleTarget); + return Sandbox(PackBuilderTestRepoPath); } - private string Clone(string sourceDirectoryPath, params string[] additionalSourcePaths) + protected string Sandbox(string sourceDirectoryPath, params string[] additionalSourcePaths) { var scd = BuildSelfCleaningDirectory(); var source = new DirectoryInfo(sourceDirectoryPath); @@ -175,14 +260,6 @@ protected string InitNewRepository(bool isBare = false) return Repository.Init(scd.DirectoryPath, isBare); } - protected Repository InitIsolatedRepository(string path = null, bool isBare = false, RepositoryOptions options = null) - { - path = path ?? InitNewRepository(isBare); - options = BuildFakeConfigs(BuildSelfCleaningDirectory(), options); - - return new Repository(path, options); - } - public void Register(string directoryPath) { directories.Add(directoryPath); @@ -190,14 +267,22 @@ public void Register(string directoryPath) public virtual void Dispose() { -#if LEAKS - GC.Collect(); -#endif - foreach (string directory in directories) { DirectoryHelper.DeleteDirectory(directory); } + +#if LEAKS_IDENTIFYING + GC.Collect(); + GC.WaitForPendingFinalizers(); + + if (Core.LeaksContainer.TypeNames.Any()) + { + Assert.Fail(string.Format("Some handles of the following types haven't been properly released: {0}.{1}" + + "In order to get some help fixing those leaks, uncomment the define LEAKS_TRACKING in Libgit2Object.cs{1}" + + "and run the tests locally.", string.Join(", ", Core.LeaksContainer.TypeNames), Environment.NewLine)); + } +#endif } protected static void InconclusiveIf(Func predicate, string message) @@ -227,7 +312,7 @@ protected void RequiresDotNetOrMonoGreaterThanOrEqualTo(System.Version minimumVe throw new InvalidOperationException("Cannot access Mono.RunTime.GetDisplayName() method."); } - var version = (string) displayName.Invoke(null, null); + var version = (string)displayName.Invoke(null, null); System.Version current; @@ -253,76 +338,59 @@ protected static void AssertValueInConfigFile(string configFilePath, string rege Assert.True(r.Success, text); } - public RepositoryOptions BuildFakeConfigs(SelfCleaningDirectory scd, RepositoryOptions options = null) - { - options = BuildFakeRepositoryOptions(scd, options); - - StringBuilder sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = global{0}", Environment.NewLine) - .AppendFormat("[Wow]{0}", Environment.NewLine) - .AppendFormat("Man-I-am-totally-global = 42{0}", Environment.NewLine); - File.WriteAllText(options.GlobalConfigurationLocation, sb.ToString()); - - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = system{0}", Environment.NewLine); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); - - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = xdg{0}", Environment.NewLine); - File.WriteAllText(options.XdgConfigurationLocation, sb.ToString()); - - return options; - } - - private static RepositoryOptions BuildFakeRepositoryOptions(SelfCleaningDirectory scd, RepositoryOptions options = null) + private static void BuildFakeRepositoryOptions(SelfCleaningDirectory scd, out string global, out string xdg, out string system, out string programData) { - options = options ?? new RepositoryOptions(); - string confs = Path.Combine(scd.DirectoryPath, "confs"); Directory.CreateDirectory(confs); - options.GlobalConfigurationLocation = Path.Combine(confs, "my-global-config"); - options.XdgConfigurationLocation = Path.Combine(confs, "my-xdg-config"); - options.SystemConfigurationLocation = Path.Combine(confs, "my-system-config"); - - return options; + global = Path.Combine(confs, "my-global-config"); + Directory.CreateDirectory(global); + xdg = Path.Combine(confs, "my-xdg-config"); + Directory.CreateDirectory(xdg); + system = Path.Combine(confs, "my-system-config"); + Directory.CreateDirectory(system); + programData = Path.Combine(confs, "my-programdata-config"); + Directory.CreateDirectory(programData); } /// /// Creates a configuration file with user.name and user.email set to signature /// /// The configuration file will be removed automatically when the tests are finished - /// The signature to use for user.name and user.email + /// The identity to use for user.name and user.email /// The path to the configuration file - protected string CreateConfigurationWithDummyUser(Signature signature) + protected void CreateConfigurationWithDummyUser(Repository repo, Identity identity) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - Directory.CreateDirectory(scd.DirectoryPath); - string configFilePath = Path.Combine(scd.DirectoryPath, "global-config"); + CreateConfigurationWithDummyUser(repo, identity.Name, identity.Email); + } - using (Configuration config = new Configuration(configFilePath)) + protected void CreateConfigurationWithDummyUser(Repository repo, string name, string email) + { + Configuration config = repo.Config; { - config.Set("user.name", signature.Name, ConfigurationLevel.Global); - config.Set("user.email", signature.Email, ConfigurationLevel.Global); + if (name != null) + { + config.Set("user.name", name); + } + + if (email != null) + { + config.Set("user.email", email); + } } - - return configFilePath; } /// /// Asserts that the commit has been authored and committed by the specified signature /// /// The commit - /// The signature to compare author and commiter to - protected void AssertCommitSignaturesAre(Commit commit, Signature signature) + /// The identity to compare author and commiter to + protected void AssertCommitIdentitiesAre(Commit commit, Identity identity) { - Assert.Equal(signature.Name, commit.Author.Name); - Assert.Equal(signature.Email, commit.Author.Email); - Assert.Equal(signature.Name, commit.Committer.Name); - Assert.Equal(signature.Email, commit.Committer.Email); + Assert.Equal(identity.Name, commit.Author.Name); + Assert.Equal(identity.Email, commit.Author.Email); + Assert.Equal(identity.Name, commit.Committer.Name); + Assert.Equal(identity.Email, commit.Committer.Email); } protected static string Touch(string parent, string file, string content = null, Encoding encoding = null) @@ -331,10 +399,19 @@ protected static string Touch(string parent, string file, string content = null, string dir = Path.GetDirectoryName(filePath); Debug.Assert(dir != null); + var newFile = !File.Exists(filePath); + Directory.CreateDirectory(dir); File.WriteAllText(filePath, content ?? string.Empty, encoding ?? Encoding.ASCII); + //Workaround for .NET Core 1.x behavior where all newly created files have execute permissions set. + //https://github.com/dotnet/corefx/issues/13342 + if (Constants.IsRunningOnUnix && newFile) + { + RemoveExecutePermissions(filePath, newFile); + } + return filePath; } @@ -346,6 +423,8 @@ protected static string Touch(string parent, string file, Stream stream) string dir = Path.GetDirectoryName(filePath); Debug.Assert(dir != null); + var newFile = !File.Exists(filePath); + Directory.CreateDirectory(dir); using (var fs = File.Open(filePath, FileMode.Create)) @@ -354,9 +433,22 @@ protected static string Touch(string parent, string file, Stream stream) fs.Flush(); } + //Work around .NET Core 1.x behavior where all newly created files have execute permissions set. + //https://github.com/dotnet/corefx/issues/13342 + if (Constants.IsRunningOnUnix && newFile) + { + RemoveExecutePermissions(filePath, newFile); + } + return filePath; } + private static void RemoveExecutePermissions(string filePath, bool newFile) + { + var process = Process.Start("chmod", $"644 {filePath}"); + process.WaitForExit(); + } + protected string Expected(string filename) { return File.ReadAllText(Path.Combine(ResourcesDirectory.FullName, "expected/" + filename)); @@ -368,8 +460,8 @@ protected string Expected(string filenameFormat, params object[] args) } protected static void AssertRefLogEntry(IRepository repo, string canonicalName, - ObjectId to, string message, ObjectId @from = null, - Signature committer = null) + string message, ObjectId @from, ObjectId to, + Identity committer, DateTimeOffset before) { var reflogEntry = repo.Refs.Log(canonicalName).First(); @@ -377,9 +469,14 @@ protected static void AssertRefLogEntry(IRepository repo, string canonicalName, Assert.Equal(message, reflogEntry.Message); Assert.Equal(@from ?? ObjectId.Zero, reflogEntry.From); - committer = committer ?? repo.Config.BuildSignature(DateTimeOffset.Now); - Assert.Equal(committer.Email, reflogEntry.Commiter.Email); - Assert.InRange(reflogEntry.Commiter.When, committer.When - TimeSpan.FromSeconds(5), committer.When); + Assert.Equal(committer.Email, reflogEntry.Committer.Email); + + // When verifying the timestamp range, give a little more room on the range. + // Git or file system datetime truncation seems to cause these stamps to jump up to a second earlier + // than we expect. See https://github.com/libgit2/libgit2sharp/issues/1764 + var low = before - TimeSpan.FromSeconds(1); + var high = DateTimeOffset.Now.TruncateMilliseconds() + TimeSpan.FromSeconds(1); + Assert.InRange(reflogEntry.Committer.When, low, high); } protected static void EnableRefLog(IRepository repository, bool enable = true) @@ -418,5 +515,10 @@ public void AssertBelongsToARepository(IRepository repo, T instance) { Assert.Same(repo, ((IBelongToARepository)instance).Repository); } + + protected void CreateAttributesFile(IRepository repo, string attributeEntry) + { + Touch(repo.Info.WorkingDirectory, ".gitattributes", attributeEntry); + } } } diff --git a/LibGit2Sharp.Tests/TestHelpers/Constants.cs b/LibGit2Sharp.Tests/TestHelpers/Constants.cs index 71f942a53..d8c14dbca 100644 --- a/LibGit2Sharp.Tests/TestHelpers/Constants.cs +++ b/LibGit2Sharp.Tests/TestHelpers/Constants.cs @@ -1,12 +1,21 @@ using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Security; +using LibGit2Sharp.Core; namespace LibGit2Sharp.Tests.TestHelpers { public static class Constants { - public const string TemporaryReposPath = "TestRepos"; + public static readonly string TemporaryReposPath = BuildPath(); public const string UnknownSha = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - public static readonly Signature Signature = new Signature("A. U. Thor", "thor@valhalla.asgard.com", new DateTimeOffset(2011, 06, 16, 10, 58, 27, TimeSpan.FromHours(2))); + public static readonly Identity Identity = new Identity("A. U. Thor", "thor@valhalla.asgard.com"); + public static readonly Identity Identity2 = new Identity("nulltoken", "emeric.fermas@gmail.com"); + + public static readonly Signature Signature = new Signature(Identity, new DateTimeOffset(2011, 06, 16, 10, 58, 27, TimeSpan.FromHours(2))); + public static readonly Signature Signature2 = new Signature(Identity2, DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100")); // Populate these to turn on live credential tests: set the // PrivateRepoUrl to the URL of a repository that requires @@ -19,15 +28,73 @@ public static class Constants // ... return new UsernamePasswordCredentials { Username = "username", Password = "swordfish" }; // // Or: + // ... return new SecureUsernamePasswordCredentials() { Username = "username", Password = StringToSecureString("swordfish") }; + // + // Or: // public const string PrivateRepoUrl = "https://tfs.contoso.com/tfs/DefaultCollection/project/_git/project"; // ... return new DefaultCredentials(); public const string PrivateRepoUrl = ""; + public static bool IsRunningOnUnix + { + get + { + return Platform.OperatingSystem == OperatingSystemType.MacOSX || + Platform.OperatingSystem == OperatingSystemType.Unix; + } + } + public static Credentials PrivateRepoCredentials(string url, string usernameFromUrl, SupportedCredentialTypes types) { return null; } + + public static string BuildPath() + { + string tempPath = null; + + const string LibGit2TestPath = "LibGit2TestPath"; + + // We're running on .Net/Windows + if (Environment.GetEnvironmentVariables().Contains(LibGit2TestPath)) + { + Trace.TraceInformation("{0} environment variable detected", LibGit2TestPath); + tempPath = Environment.GetEnvironmentVariables()[LibGit2TestPath] as string; + } + + if (string.IsNullOrWhiteSpace(tempPath) || !Directory.Exists(tempPath)) + { + Trace.TraceInformation("Using default test path value"); + tempPath = Path.GetTempPath(); + } + + //workaround macOS symlinking its temp folder + if (tempPath.StartsWith("/var")) + { + tempPath = "/private" + tempPath; + } + + string testWorkingDirectory = Path.Combine(tempPath, "LibGit2Sharp-TestRepos"); + Trace.TraceInformation("Test working directory set to '{0}'", testWorkingDirectory); + return testWorkingDirectory; + } + + // To help with creating secure strings to test with. + internal static SecureString StringToSecureString(string str) + { + var chars = str.ToCharArray(); + + var secure = new SecureString(); + for (var i = 0; i < chars.Length; i++) + { + secure.AppendChar(chars[i]); + } + + secure.MakeReadOnly(); + + return secure; + } } } diff --git a/LibGit2Sharp.Tests/TestHelpers/DateTimeOffsetExtensions.cs b/LibGit2Sharp.Tests/TestHelpers/DateTimeOffsetExtensions.cs new file mode 100644 index 000000000..14b5b06f9 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/DateTimeOffsetExtensions.cs @@ -0,0 +1,9 @@ +using System; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public static class DateTimeOffsetExtensions + { + public static DateTimeOffset TruncateMilliseconds(this DateTimeOffset dto) => new DateTimeOffset(dto.Year, dto.Month, dto.Day, dto.Hour, dto.Minute, dto.Second, dto.Offset); + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs index 44bc9bd9d..636d4f198 100644 --- a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs +++ b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; namespace LibGit2Sharp.Tests.TestHelpers { @@ -13,6 +16,8 @@ public static class DirectoryHelper { "gitmodules", ".gitmodules" }, }; + private static readonly Type[] whitelist = { typeof(IOException), typeof(UnauthorizedAccessException) }; + public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) { // From http://stackoverflow.com/questions/58744/best-way-to-copy-the-entire-contents-of-a-directory-in-c/58779#58779 @@ -32,55 +37,67 @@ private static string Rename(string name) return toRename.ContainsKey(name) ? toRename[name] : name; } - public static void DeleteSubdirectories(string parentPath) - { - string[] dirs = Directory.GetDirectories(parentPath); - foreach (string dir in dirs) - { - DeleteDirectory(dir); - } - } - public static void DeleteDirectory(string directoryPath) { // From http://stackoverflow.com/questions/329355/cannot-delete-directory-with-directory-deletepath-true/329502#329502 if (!Directory.Exists(directoryPath)) { - Trace.WriteLine( - string.Format("Directory '{0}' is missing and can't be removed.", - directoryPath)); - + Trace.WriteLine(string.Format("Directory '{0}' is missing and can't be removed.", directoryPath)); return; } + NormalizeAttributes(directoryPath); + DeleteDirectory(directoryPath, maxAttempts: 5, initialTimeout: 16, timeoutFactor: 2); + } - string[] files = Directory.GetFiles(directoryPath); - string[] dirs = Directory.GetDirectories(directoryPath); + private static void NormalizeAttributes(string directoryPath) + { + string[] filePaths = Directory.GetFiles(directoryPath); + string[] subdirectoryPaths = Directory.GetDirectories(directoryPath); - foreach (string file in files) + foreach (string filePath in filePaths) { - File.SetAttributes(file, FileAttributes.Normal); - File.Delete(file); + File.SetAttributes(filePath, FileAttributes.Normal); } - - foreach (string dir in dirs) + foreach (string subdirectoryPath in subdirectoryPaths) { - DeleteDirectory(dir); + NormalizeAttributes(subdirectoryPath); } - File.SetAttributes(directoryPath, FileAttributes.Normal); - try - { - Directory.Delete(directoryPath, false); - } - catch (IOException) + } + + private static void DeleteDirectory(string directoryPath, int maxAttempts, int initialTimeout, int timeoutFactor) + { + for (int attempt = 1; attempt <= maxAttempts; attempt++) { - Trace.WriteLine(string.Format("{0}The directory '{1}' could not be deleted!" + - "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." + - "{0}Known and common causes include:" + - "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" + - "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus){0}", - Environment.NewLine, Path.GetFullPath(directoryPath))); + try + { + Directory.Delete(directoryPath, true); + return; + } + catch (Exception ex) + { + var caughtExceptionType = ex.GetType(); + + if (!whitelist.Any(knownExceptionType => knownExceptionType.GetTypeInfo().IsAssignableFrom(caughtExceptionType))) + { + throw; + } + + if (attempt < maxAttempts) + { + Thread.Sleep(initialTimeout * (int)Math.Pow(timeoutFactor, attempt - 1)); + continue; + } + + Trace.WriteLine(string.Format("{0}The directory '{1}' could not be deleted ({2} attempts were made) due to a {3}: {4}" + + "{0}Most of the time, this is due to an external process accessing the files in the temporary repositories created during the test runs, and keeping a handle on the directory, thus preventing the deletion of those files." + + "{0}Known and common causes include:" + + "{0}- Windows Search Indexer (go to the Indexing Options, in the Windows Control Panel, and exclude the bin folder of LibGit2Sharp.Tests)" + + "{0}- Antivirus (exclude the bin folder of LibGit2Sharp.Tests from the paths scanned by your real-time antivirus)" + + "{0}- TortoiseGit (change the 'Icon Overlays' settings, e.g., adding the bin folder of LibGit2Sharp.Tests to 'Exclude paths' and appending an '*' to exclude all subfolders as well)", + Environment.NewLine, Path.GetFullPath(directoryPath), maxAttempts, caughtExceptionType, ex.Message)); + } } } } diff --git a/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs b/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs new file mode 100644 index 000000000..b03b8d2c1 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/FileExportFilter.cs @@ -0,0 +1,96 @@ +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + class FileExportFilter : Filter + { + public int CleanCalledCount = 0; + public int CompleteCalledCount = 0; + public int SmudgeCalledCount = 0; + public readonly HashSet FilesFiltered; + + private bool clean; + + public FileExportFilter(string name, IEnumerable attributes) + : base(name, attributes) + { + FilesFiltered = new HashSet(); + } + + protected override void Create(string path, string root, FilterMode mode) + { + if (mode == FilterMode.Clean) + { + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + if (File.Exists(cachePath)) + { + File.Delete(cachePath); + } + } + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + CleanCalledCount++; + + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + using (var file = File.Exists(cachePath) ? File.Open(cachePath, FileMode.Append, FileAccess.Write, FileShare.None) : File.Create(cachePath)) + { + input.CopyTo(file); + } + + clean = true; + } + + protected override void Complete(string path, string root, Stream output) + { + CompleteCalledCount++; + + string filename = Path.GetFileName(path); + string cachePath = Path.Combine(root, ".git", filename); + + if (clean) + { + byte[] bytes = Encoding.UTF8.GetBytes(path); + output.Write(bytes, 0, bytes.Length); + FilesFiltered.Add(path); + } + else + { + if (File.Exists(cachePath)) + { + using (var file = File.Open(cachePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)) + { + file.CopyTo(output); + } + } + } + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + SmudgeCalledCount++; + + StringBuilder text = new StringBuilder(); + + byte[] buffer = new byte[64 * 1024]; + int read; + while ((read = input.Read(buffer, 0, buffer.Length)) > 0) + { + string decoded = Encoding.UTF8.GetString(buffer, 0, read); + text.Append(decoded); + } + + if (!FilesFiltered.Contains(text.ToString())) + throw new FileNotFoundException(); + + clean = false; + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs b/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs new file mode 100644 index 000000000..7c2855528 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/ProcessHelper.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace LibGit2Sharp.Tests +{ + public static class ProcessHelper + { + public static (string, int) RunProcess(string fileName, string arguments, string workingDirectory = null) + { + var process = new Process + { + StartInfo = new ProcessStartInfo(fileName, arguments) + { + RedirectStandardError = true, + RedirectStandardOutput = true, + CreateNoWindow = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? string.Empty + } + }; + + var output = new StringBuilder(); + + process.OutputDataReceived += (_, e) => output.AppendLine(e.Data); + process.ErrorDataReceived += (_, e) => output.AppendLine(e.Data); + + process.Start(); + + process.WaitForExit(); + + return (output.ToString(), process.ExitCode); + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/SelfCleaningDirectory.cs b/LibGit2Sharp.Tests/TestHelpers/SelfCleaningDirectory.cs index 1c292cbb3..5abc9365f 100644 --- a/LibGit2Sharp.Tests/TestHelpers/SelfCleaningDirectory.cs +++ b/LibGit2Sharp.Tests/TestHelpers/SelfCleaningDirectory.cs @@ -28,7 +28,7 @@ public SelfCleaningDirectory(IPostTestDirectoryRemover directoryRemover, string protected static string BuildTempPath() { - return Path.Combine(Constants.TemporaryReposPath, Guid.NewGuid().ToString().Substring(0, 8)); + return Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); } } } diff --git a/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs b/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs deleted file mode 100644 index b16d823f1..000000000 --- a/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs +++ /dev/null @@ -1,144 +0,0 @@ -using System; -//********************************************************************** -//* This file is based on the DynamicSkipExample.cs in xUnit which is -//* provided under the following Ms-PL license: -//* -//* This license governs use of the accompanying software. If you use -//* the software, you accept this license. If you do not accept the -//* license, do not use the software. -//* -//* 1. Definitions -//* -//* The terms "reproduce," "reproduction," "derivative works," and -//* "distribution" have the same meaning here as under U.S. copyright -//* law. -//* -//* A "contribution" is the original software, or any additions or -//* changes to the software. -//* -//* A "contributor" is any person that distributes its contribution -//* under this license. -//* -//* "Licensed patents" are a contributor's patent claims that read -//* directly on its contribution. -//* -//* 2. Grant of Rights -//* -//* (A) Copyright Grant- Subject to the terms of this license, including -//* the license conditions and limitations in section 3, each -//* contributor grants you a non-exclusive, worldwide, royalty-free -//* copyright license to reproduce its contribution, prepare derivative -//* works of its contribution, and distribute its contribution or any -//* derivative works that you create. -//* -//* (B) Patent Grant- Subject to the terms of this license, including -//* the license conditions and limitations in section 3, each -//* contributor grants you a non-exclusive, worldwide, royalty-free -//* license under its licensed patents to make, have made, use, sell, -//* offer for sale, import, and/or otherwise dispose of its contribution -//* in the software or derivative works of the contribution in the -//* software. -//* -//* 3. Conditions and Limitations -//* -//* (A) No Trademark License- This license does not grant you rights to -//* use any contributors' name, logo, or trademarks. -//* -//* (B) If you bring a patent claim against any contributor over patents -//* that you claim are infringed by the software, your patent license -//* from such contributor to the software ends automatically. -//* -//* (C) If you distribute any portion of the software, you must retain -//* all copyright, patent, trademark, and attribution notices that are -//* present in the software. -//* -//* (D) If you distribute any portion of the software in source code -//* form, you may do so only under this license by including a complete -//* copy of this license with your distribution. If you distribute any -//* portion of the software in compiled or object code form, you may -//* only do so under a license that complies with this license. -//********************************************************************** -using System.Collections.Generic; -using System.Linq; -using System.Xml; -using Xunit; -using Xunit.Extensions; -using Xunit.Sdk; - -namespace LibGit2Sharp.Tests.TestHelpers -{ - class SkippableFactAttribute : FactAttribute - { - protected override IEnumerable EnumerateTestCommands(IMethodInfo method) - { - return base.EnumerateTestCommands(method).Select(SkippableTestCommand.Wrap(method)); - } - } - - class SkippableTheoryAttribute : TheoryAttribute - { - protected override IEnumerable EnumerateTestCommands(IMethodInfo method) - { - return base.EnumerateTestCommands(method).Select(SkippableTestCommand.Wrap(method)); - } - } - - class SkippableTestCommand : ITestCommand - { - public static Func Wrap(IMethodInfo method) - { - return c => new SkippableTestCommand(method, c); - } - - private readonly IMethodInfo method; - private readonly ITestCommand inner; - - private SkippableTestCommand(IMethodInfo method, ITestCommand inner) - { - this.method = method; - this.inner = inner; - } - - public MethodResult Execute(object testClass) - { - try - { - return inner.Execute(testClass); - } - catch (SkipException e) - { - return new SkipResult(method, DisplayName, e.Reason); - } - } - - public XmlNode ToStartXml() - { - return inner.ToStartXml(); - } - - public string DisplayName - { - get { return inner.DisplayName; } - } - - public bool ShouldCreateInstance - { - get { return inner.ShouldCreateInstance; } - } - - public int Timeout - { - get { return inner.Timeout; } - } - } - - class SkipException : Exception - { - public SkipException(string reason) - { - Reason = reason; - } - - public string Reason { get; private set; } - } -} diff --git a/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs b/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs new file mode 100644 index 000000000..2cba06d49 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/SubstitutionCipherFilter.cs @@ -0,0 +1,47 @@ +using System.Collections.Generic; +using System.IO; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public class SubstitutionCipherFilter : Filter + { + public int CleanCalledCount = 0; + public int SmudgeCalledCount = 0; + + public SubstitutionCipherFilter(string name, IEnumerable attributes) + : base(name, attributes) + { + } + + protected override void Clean(string path, string root, Stream input, Stream output) + { + CleanCalledCount++; + RotateByThirteenPlaces(input, output); + } + + protected override void Smudge(string path, string root, Stream input, Stream output) + { + SmudgeCalledCount++; + RotateByThirteenPlaces(input, output); + } + + public static void RotateByThirteenPlaces(Stream input, Stream output) + { + int value; + + while ((value = input.ReadByte()) != -1) + { + if ((value >= 'a' && value <= 'm') || (value >= 'A' && value <= 'M')) + { + value += 13; + } + else if ((value >= 'n' && value <= 'z') || (value >= 'N' && value <= 'Z')) + { + value -= 13; + } + + output.WriteByte((byte)value); + } + } + } +} diff --git a/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs b/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs new file mode 100644 index 000000000..a3e1e58c4 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/TestRemoteRefs.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public class TestRemoteRefs + { + /* + * git ls-remote http://github.com/libgit2/TestGitRepository + * 49322bb17d3acc9146f98c97d078513228bbf3c0 HEAD + * 0966a434eb1a025db6b71485ab63a3bfbea520b6 refs/heads/first-merge + * 49322bb17d3acc9146f98c97d078513228bbf3c0 refs/heads/master + * 42e4e7c5e507e113ebbb7801b16b52cf867b7ce1 refs/heads/no-parent + * d96c4e80345534eccee5ac7b07fc7603b56124cb refs/tags/annotated_tag + * c070ad8c08840c8116da865b2d65593a6bb9cd2a refs/tags/annotated_tag^{} + * 55a1a760df4b86a02094a904dfa511deb5655905 refs/tags/blob + * 8f50ba15d49353813cc6e20298002c0d17b0a9ee refs/tags/commit_tree + * 6e0c7bdb9b4ed93212491ee778ca1c65047cab4e refs/tags/nearly-dangling + */ + /// + /// Expected references on http://github.com/libgit2/TestGitRepository + /// + public static List> ExpectedRemoteRefs = new List>() + { + new Tuple("HEAD", "49322bb17d3acc9146f98c97d078513228bbf3c0"), + new Tuple("refs/heads/first-merge", "0966a434eb1a025db6b71485ab63a3bfbea520b6"), + new Tuple("refs/heads/master", "49322bb17d3acc9146f98c97d078513228bbf3c0"), + new Tuple("refs/heads/no-parent", "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1"), + new Tuple("refs/tags/annotated_tag", "d96c4e80345534eccee5ac7b07fc7603b56124cb"), + new Tuple("refs/tags/annotated_tag^{}", "c070ad8c08840c8116da865b2d65593a6bb9cd2a"), + new Tuple("refs/tags/blob", "55a1a760df4b86a02094a904dfa511deb5655905"), + new Tuple("refs/tags/commit_tree", "8f50ba15d49353813cc6e20298002c0d17b0a9ee"), + new Tuple("refs/tags/nearly-dangling", "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e"), + }; + } +} diff --git a/LibGit2Sharp.Tests/TreeDefinitionFixture.cs b/LibGit2Sharp.Tests/TreeDefinitionFixture.cs index b43e6131d..6c0c0a41a 100644 --- a/LibGit2Sharp.Tests/TreeDefinitionFixture.cs +++ b/LibGit2Sharp.Tests/TreeDefinitionFixture.cs @@ -18,7 +18,8 @@ public class TreeDefinitionFixture : BaseFixture [Fact] public void CanBuildATreeDefinitionFromATree() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.NotNull(td); @@ -34,7 +35,8 @@ public void BuildingATreeDefinitionWithBadParamsThrows() [Fact] public void RequestingANonExistingEntryReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -48,7 +50,8 @@ public void RequestingANonExistingEntryReturnsNull() [Fact] public void RequestingAnEntryWithBadParamsThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -64,13 +67,14 @@ public void RequestingAnEntryWithBadParamsThrows() [Theory] [InlineData("1/branch_file.txt", "100755", TreeEntryTargetType.Blob, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057")] - [InlineData("README", "100644", TreeEntryTargetType.Blob, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")] - [InlineData("branch_file.txt", "100644", TreeEntryTargetType.Blob, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057")] - [InlineData("new.txt", "100644", TreeEntryTargetType.Blob, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")] - [InlineData("1", "040000", TreeEntryTargetType.Tree, "7f76480d939dc401415927ea7ef25c676b8ddb8f")] + [InlineData("README", "100644", TreeEntryTargetType.Blob, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")] + [InlineData("branch_file.txt", "100644", TreeEntryTargetType.Blob, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057")] + [InlineData("new.txt", "100644", TreeEntryTargetType.Blob, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")] + [InlineData("1", "040000", TreeEntryTargetType.Tree, "7f76480d939dc401415927ea7ef25c676b8ddb8f")] public void CanRetrieveEntries(string path, string expectedAttributes, TreeEntryTargetType expectedType, string expectedSha) { - using (var repo = new Repository(BareTestRepoPath)) + string repoPath = SandboxBareTestRepo(); + using (var repo = new Repository(repoPath)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -100,7 +104,8 @@ private static Mode ToMode(string expectedAttributes) [InlineData("1", "2/3")] public void CanAddAnExistingTreeEntryDefinition(string sourcePath, string targetPath) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Null(td[targetPath]); @@ -121,7 +126,8 @@ public void CanAddAnExistingGitLinkTreeEntryDefinition() const string sourcePath = "sm_unchanged"; const string targetPath = "sm_from_td"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Null(td[targetPath]); @@ -136,6 +142,17 @@ public void CanAddAnExistingGitLinkTreeEntryDefinition() } } + private const string StringOf40Chars = "0123456789012345678901234567890123456789"; + + /// + /// Used to verify that windows path limitation to 260 chars is not limiting the size of + /// the keys present in the object database. + /// + private const string StringOf600Chars = + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars + StringOf40Chars; + [Theory] [InlineData("README", "README_TOO")] [InlineData("README", "1/README")] @@ -146,9 +163,13 @@ public void CanAddAnExistingGitLinkTreeEntryDefinition() [InlineData("1/branch_file.txt", "1/2/3/another_one.txt")] [InlineData("1", "2")] [InlineData("1", "2/3")] - public void CanAddAnExistingTreeEntry(string sourcePath, string targetPath) + [InlineData("1", "C:\\/10")] + [InlineData("1", " : * ? \" < > |")] + [InlineData("1", StringOf600Chars)] + public void CanAddAndRemoveAnExistingTreeEntry(string sourcePath, string targetPath) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Head.Tip.Tree; var td = TreeDefinition.From(tree); @@ -161,6 +182,36 @@ public void CanAddAnExistingTreeEntry(string sourcePath, string targetPath) Assert.NotNull(fetched); Assert.Equal(te.Target.Id, fetched.TargetId); + + // Ensuring that the object database can handle uncommon paths. + var newTree = repo.ObjectDatabase.CreateTree(td); + Assert.Equal(newTree[targetPath].Target.Id, te.Target.Id); + + td.Remove(targetPath); + Assert.Null(td[targetPath]); + } + } + + [Theory] + [InlineData("C:\\")] + [InlineData(" : * ? \" \n < > |")] + [InlineData("a\\b")] + [InlineData("\\\\b\a")] + [InlineData("éàµ")] + [InlineData(StringOf600Chars)] + public void TreeNamesCanContainCharsForbiddenOnSomeOS(string targetName) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var pointedItem = repo.Head.Tip.Tree; + + var td = new TreeDefinition(); + td.Add(targetName, pointedItem); + + var newTree = repo.ObjectDatabase.CreateTree(td); + Assert.Equal(newTree[targetName].Target.Sha, pointedItem.Sha); + Assert.Equal(newTree[targetName].Name, targetName); } } @@ -172,7 +223,8 @@ public void CanAddAnExistingGitLinkTreeEntry(string targetPath) { const string sourcePath = "sm_unchanged"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Head.Tip.Tree; var td = TreeDefinition.From(tree); @@ -195,7 +247,8 @@ public void CanAddAnExistingGitLinkTreeEntry(string targetPath) [InlineData("45b983be36b73c0788dc9cbcb76cbb80fc7bb057", "another_one.txt")] public void CanAddAnExistingBlob(string blobSha, string targetPath) { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Null(td[targetPath]); @@ -213,12 +266,56 @@ public void CanAddAnExistingBlob(string blobSha, string targetPath) } } + [Theory] + [InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "README_TOO")] + [InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "1/README")] + [InlineData("45b983be36b73c0788dc9cbcb76cbb80fc7bb057", "1/another_one.txt")] + [InlineData("45b983be36b73c0788dc9cbcb76cbb80fc7bb057", "another_one.txt")] + public void CanAddBlobById(string blobSha, string targetPath) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.Null(td[targetPath]); + + var objectId = new ObjectId(blobSha); + + td.Add(targetPath, objectId, Mode.NonExecutableFile); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(objectId, fetched.TargetId); + Assert.Equal(Mode.NonExecutableFile, fetched.Mode); + } + } + + [Fact] + public void CannotAddTreeById() + { + const string treeSha = "7f76480d939dc401415927ea7ef25c676b8ddb8f"; + const string targetPath = "1/2"; + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.Null(td[targetPath]); + + var objectId = new ObjectId(treeSha); + + Assert.Throws(() => td.Add(targetPath, objectId, Mode.Directory)); + } + } + [Fact] public void CanAddAnExistingSubmodule() { const string submodulePath = "sm_unchanged"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { var submodule = repo.Submodules[submodulePath]; Assert.NotNull(submodule); @@ -246,7 +343,8 @@ public void CanAddAnExistingTree() const string treeSha = "7f76480d939dc401415927ea7ef25c676b8ddb8f"; const string targetPath = "1/2"; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -271,7 +369,8 @@ public void CanReplaceAnExistingTreeWithABlob() const string blobSha = "a8233120f6ad708f843d861ce2b7228ec4e3dec6"; const string targetPath = "1"; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); @@ -301,7 +400,8 @@ public void CanReplaceAnExistingBlobWithATree(string targetPath) { const string treeSha = "7f76480d939dc401415927ea7ef25c676b8ddb8f"; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.NotNull(td[targetPath]); @@ -327,7 +427,8 @@ public void CanReplaceAnExistingTreeWithAGitLink() var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; const string targetPath = "just_a_dir"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); @@ -353,7 +454,8 @@ public void CanReplaceAnExistingGitLinkWithATree() const string treeSha = "607d96653d4d0a4f733107f7890c2e67b55b620d"; const string targetPath = "sm_unchanged"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.NotNull(td[targetPath]); @@ -380,7 +482,8 @@ public void CanReplaceAnExistingBlobWithAGitLink() var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; const string targetPath = "just_a_file"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.NotNull(td[targetPath]); @@ -403,7 +506,8 @@ public void CanReplaceAnExistingGitLinkWithABlob() const string blobSha = "42cfb95cd01bf9225b659b5ee3edcc78e8eeb478"; const string targetPath = "sm_unchanged"; - using (var repo = new Repository(SubmoduleTestRepoWorkingDirPath)) + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.NotNull(td[targetPath]); @@ -427,7 +531,8 @@ public void CanReplaceAnExistingGitLinkWithABlob() [Fact] public void CanNotReplaceAnExistingTreeWithATreeBeingAssembled() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); Assert.Equal(TreeEntryTargetType.Tree, td["1"].TargetType); @@ -446,7 +551,8 @@ public void ModifyingTheContentOfATreeSetsItsOidToNull() const string blobSha = "a8233120f6ad708f843d861ce2b7228ec4e3dec6"; const string targetPath = "1/another_branch_file.txt"; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); @@ -464,7 +570,8 @@ public void ModifyingTheContentOfATreeSetsItsOidToNull() [Fact] public void CanAddAnExistingBlobEntryWithAnExistingTree() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); TreeEntryDefinition original = td["README"]; @@ -480,5 +587,23 @@ public void CanAddAnExistingBlobEntryWithAnExistingTree() Assert.NotNull(td["1/branch_file.txt"]); } } + + [Fact] + public void CanRemoveADirectoryWithChildren() + { + const string blobSha = "a8233120f6ad708f843d861ce2b7228ec4e3dec6"; + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = new TreeDefinition(); + var blob = repo.Lookup(blobSha); + td.Add("folder/subfolder/file1", blob, Mode.NonExecutableFile); + td.Add("folder/file1", blob, Mode.NonExecutableFile); + td.Remove("folder"); + Assert.Null(td["folder"]); + Tree t = repo.ObjectDatabase.CreateTree(td); + Assert.Null(t["folder"]); + } + } } } diff --git a/LibGit2Sharp.Tests/TreeFixture.cs b/LibGit2Sharp.Tests/TreeFixture.cs index ced6f2788..a3a8d89eb 100644 --- a/LibGit2Sharp.Tests/TreeFixture.cs +++ b/LibGit2Sharp.Tests/TreeFixture.cs @@ -13,9 +13,11 @@ public class TreeFixture : BaseFixture [Fact] public void CanCompareTwoTreeEntries() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); TreeEntry treeEntry1 = tree["README"]; TreeEntry treeEntry2 = tree["README"]; Assert.Equal(treeEntry2, treeEntry1); @@ -26,9 +28,11 @@ public void CanCompareTwoTreeEntries() [Fact] public void CanConvertEntryToBlob() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); TreeEntry treeEntry = tree["README"]; var blob = treeEntry.Target as Blob; @@ -39,9 +43,11 @@ public void CanConvertEntryToBlob() [Fact] public void CanConvertEntryToTree() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); TreeEntry treeEntry = tree["1"]; var subtree = treeEntry.Target as Tree; @@ -52,9 +58,11 @@ public void CanConvertEntryToTree() [Fact] public void CanEnumerateBlobs() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); IEnumerable blobs = tree .Where(e => e.TargetType == TreeEntryTargetType.Blob) @@ -68,25 +76,29 @@ public void CanEnumerateBlobs() [Fact] public void CanEnumerateSubTrees() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); IEnumerable subTrees = tree .Where(e => e.TargetType == TreeEntryTargetType.Tree) .Select(e => e.Target) .Cast(); - Assert.Equal(1, subTrees.Count()); + Assert.Single(subTrees); } } [Fact] public void CanEnumerateTreeEntries() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); Assert.Equal(tree.Count, tree.Count()); Assert.Equal(new[] { "1", "README", "branch_file.txt", "new.txt" }, tree.Select(te => te.Name).ToArray()); @@ -96,9 +108,11 @@ public void CanEnumerateTreeEntries() [Fact] public void CanGetEntryByName() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); TreeEntry treeEntry = tree["README"]; Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", treeEntry.Target.Sha); Assert.Equal("README", treeEntry.Name); @@ -108,9 +122,11 @@ public void CanGetEntryByName() [Fact] public void GettingAnUknownTreeEntryReturnsNull() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); TreeEntry treeEntry = tree["I-do-not-exist"]; Assert.Null(treeEntry); } @@ -119,9 +135,11 @@ public void GettingAnUknownTreeEntryReturnsNull() [Fact] public void CanGetEntryCountFromTree() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); Assert.Equal(4, tree.Count); } } @@ -129,9 +147,11 @@ public void CanGetEntryCountFromTree() [Fact] public void CanReadEntryAttributes() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); + Assert.False(tree.IsMissing); Assert.Equal(Mode.NonExecutableFile, tree["README"].Mode); } } @@ -139,35 +159,54 @@ public void CanReadEntryAttributes() [Fact] public void CanReadTheTreeData() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var tree = repo.Lookup(sha); Assert.NotNull(tree); + Assert.False(tree.IsMissing); } } [Fact] public void TreeDataIsPresent() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject tree = repo.Lookup(sha); Assert.NotNull(tree); + Assert.False(tree.IsMissing); } } [Fact] - public void CanRetrieveTreeEntryPath() + public void TreeUsesPosixStylePaths() { using (var repo = new Repository(BareTestRepoPath)) { /* From a commit tree */ var commitTree = repo.Lookup("4c062a6").Tree; + Assert.False(commitTree.IsMissing); + Assert.NotNull(commitTree["1/branch_file.txt"]); + Assert.Null(commitTree["1\\branch_file.txt"]); + } + } + + [Fact] + public void CanRetrieveTreeEntryPath() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + /* From a commit tree */ + var commitTree = repo.Lookup("4c062a6").Tree; + Assert.False(commitTree.IsMissing); TreeEntry treeTreeEntry = commitTree["1"]; Assert.Equal("1", treeTreeEntry.Path); - string completePath = Path.Combine("1", "branch_file.txt"); + string completePath = "1/branch_file.txt"; TreeEntry blobTreeEntry = commitTree["1/branch_file.txt"]; Assert.Equal(completePath, blobTreeEntry.Path); @@ -176,6 +215,7 @@ public void CanRetrieveTreeEntryPath() // tree but exposes a complete path through its Path property var subTree = treeTreeEntry.Target as Tree; Assert.NotNull(subTree); + Assert.False(subTree.IsMissing); TreeEntry anInstance = subTree["branch_file.txt"]; Assert.NotEqual("branch_file.txt", anInstance.Path); @@ -204,7 +244,7 @@ public void CanRetrieveTreeEntryPath() [Fact] public void CanParseSymlinkTreeEntries() { - var path = CloneBareTestRepo(); + var path = SandboxBareTestRepo(); using (var repo = new Repository(path)) { @@ -214,6 +254,7 @@ public void CanParseSymlinkTreeEntries() .Add("A symlink", linkContent, Mode.SymbolicLink); Tree t = repo.ObjectDatabase.CreateTree(td); + Assert.False(t.IsMissing); var te = t["A symlink"]; @@ -223,5 +264,31 @@ public void CanParseSymlinkTreeEntries() Assert.Equal(linkContent, te.Target); } } + + [Fact] + public void CanTellIfATreeIsMissing() + { + var path = SandboxBareTestRepo(); + + // Manually delete the objects directory to simulate a partial clone + Directory.Delete(Path.Combine(path, "objects", "fd"), true); + + using (var repo = new Repository(path)) + { + // Look up for the commit that reference the tree which is now missing + var commit = repo.Lookup("4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); + + Assert.True(commit.Tree.IsMissing); + Assert.Equal("fd093bff70906175335656e6ce6ae05783708765", commit.Tree.Sha); + Assert.Throws(() => commit.Tree.Count); + Assert.Throws(() => commit.Tree.Count()); + Assert.Throws(() => commit.Tree["README"]); + Assert.Throws(() => commit.Tree.ToArray()); + Assert.Throws(() => + { + foreach (var _ in commit.Tree) { } + }); + } + } } } diff --git a/LibGit2Sharp.Tests/UnstageFixture.cs b/LibGit2Sharp.Tests/UnstageFixture.cs index d58784423..1eeee0e72 100644 --- a/LibGit2Sharp.Tests/UnstageFixture.cs +++ b/LibGit2Sharp.Tests/UnstageFixture.cs @@ -13,7 +13,7 @@ public class UnstageFixture : BaseFixture [Fact] public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOfHead() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; @@ -25,12 +25,12 @@ public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOf string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); File.AppendAllText(fullpath, "Is there there anybody out there?"); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); Assert.Equal(count, repo.Index.Count); Assert.NotEqual((blobId), repo.Index[posixifiedFileName].Id); - repo.Index.Unstage(posixifiedFileName); + Commands.Unstage(repo, posixifiedFileName); Assert.Equal(count, repo.Index.Count); Assert.Equal(blobId, repo.Index[posixifiedFileName].Id); @@ -40,7 +40,7 @@ public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOf [Fact] public void CanStageAndUnstageAnIgnoredFile() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Touch(repo.Info.WorkingDirectory, ".gitignore", "*.ign" + Environment.NewLine); @@ -48,73 +48,93 @@ public void CanStageAndUnstageAnIgnoredFile() const string relativePath = "Champa.ign"; Touch(repo.Info.WorkingDirectory, relativePath, "On stage!" + Environment.NewLine); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); - repo.Index.Stage(relativePath, new StageOptions { IncludeIgnored = true }); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(relativePath)); + Commands.Stage(repo, relativePath, new StageOptions { IncludeIgnored = true }); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(relativePath)); - repo.Index.Unstage(relativePath); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); + Commands.Unstage(repo, relativePath); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); } } [Theory] [InlineData("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] - [InlineData("deleted_unstaged_file.txt", FileStatus.Missing, true, FileStatus.Missing, true, 0)] - [InlineData("modified_unstaged_file.txt", FileStatus.Modified, true, FileStatus.Modified, true, 0)] - [InlineData("modified_staged_file.txt", FileStatus.Staged, true, FileStatus.Modified, true, 0)] - [InlineData("new_tracked_file.txt", FileStatus.Added, true, FileStatus.Untracked, false, -1)] - [InlineData("deleted_staged_file.txt", FileStatus.Removed, false, FileStatus.Missing, true, 1)] + [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, true, FileStatus.DeletedFromWorkdir, true, 0)] + [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, true, FileStatus.ModifiedInWorkdir, true, 0)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, true, FileStatus.ModifiedInWorkdir, true, 0)] + [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, true, FileStatus.NewInWorkdir, false, -1)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex, false, FileStatus.DeletedFromWorkdir, true, 1)] public void CanUnstage( string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - repo.Index.Unstage(relativePath); + Commands.Unstage(repo, relativePath); Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); - Assert.Equal(expectedStatusOnceStaged, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(expectedStatusOnceStaged, repo.RetrieveStatus(relativePath)); } } + [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("modified_staged_file.txt", FileStatus.ModifiedInWorkdir)] + [InlineData("new_tracked_file.txt", FileStatus.NewInWorkdir)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromWorkdir)] + public void UnstagingWritesIndex(string relativePath, FileStatus expectedStatus) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Commands.Unstage(repo, relativePath); + } + + using (var repo = new Repository(path)) + { + Assert.Equal(expectedStatus, repo.RetrieveStatus(relativePath)); + } + } + + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("where-am-I.txt", FileStatus.Nonexistent)] public void UnstagingUnknownPathsWithStrictUnmatchedExplicitPathsValidationThrows(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Index.Unstage(relativePath, new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("where-am-I.txt", FileStatus.Nonexistent)] public void CanUnstageUnknownPathsWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.DoesNotThrow(() => repo.Index.Unstage(relativePath, new ExplicitPathsOptions() { ShouldFailOnUnmatchedPath = false })); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions() { ShouldFailOnUnmatchedPath = false }); + + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); } } [Fact] public void CanUnstageTheRemovalOfAFile() { - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { int count = repo.Index.Count; @@ -124,12 +144,12 @@ public void CanUnstageTheRemovalOfAFile() string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); Assert.False(File.Exists(fullPath)); - Assert.Equal(FileStatus.Removed, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(filename)); - repo.Index.Unstage(filename); + Commands.Unstage(repo, filename); Assert.Equal(count + 1, repo.Index.Count); - Assert.Equal(FileStatus.Missing, repo.Index.RetrieveStatus(filename)); + Assert.Equal(FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); } } @@ -143,48 +163,49 @@ public void CanUnstageUntrackedFileAgainstAnOrphanedHead() const string relativePath = "a.txt"; Touch(repo.Info.WorkingDirectory, relativePath, "hello test file\n"); - repo.Index.Stage(relativePath); + Commands.Stage(repo, relativePath); - repo.Index.Unstage(relativePath); - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.Equal(0, status.Staged.Count()); - Assert.Equal(1, status.Untracked.Count()); + Commands.Unstage(repo, relativePath); + RepositoryStatus status = repo.RetrieveStatus(); + Assert.Empty(status.Staged); + Assert.Single(status.Untracked); - Assert.Throws(() => repo.Index.Unstage("i-dont-exist", new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, "i-dont-exist", new ExplicitPathsOptions())); } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("where-am-I.txt", FileStatus.Nonexistent)] public void UnstagingUnknownPathsAgainstAnOrphanedHeadWithStrictUnmatchedExplicitPathsValidationThrows(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { repo.Refs.UpdateTarget("HEAD", "refs/heads/orphaned"); Assert.True(repo.Info.IsHeadUnborn); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); - Assert.Throws(() => repo.Index.Unstage(relativePath, new ExplicitPathsOptions())); + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); } } [Theory] - [InlineData("new_untracked_file.txt", FileStatus.Untracked)] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] [InlineData("where-am-I.txt", FileStatus.Nonexistent)] public void CanUnstageUnknownPathsAgainstAnOrphanedHeadWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus currentStatus) { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { repo.Refs.UpdateTarget("HEAD", "refs/heads/orphaned"); Assert.True(repo.Info.IsHeadUnborn); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Commands.Unstage(repo, relativePath); + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - Assert.DoesNotThrow(() => repo.Index.Unstage(relativePath)); - Assert.DoesNotThrow(() => repo.Index.Unstage(relativePath, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); } } @@ -192,7 +213,7 @@ public void CanUnstageUnknownPathsAgainstAnOrphanedHeadWithLaxUnmatchedExplicitP public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string path = CloneStandardTestRepo(); + string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); @@ -200,7 +221,7 @@ public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() const string filename = "unit_test.txt"; string fullPath = Touch(di.FullName, filename, "some contents"); - Assert.Throws(() => repo.Index.Unstage(fullPath)); + Assert.Throws(() => Commands.Unstage(repo, fullPath)); } } @@ -218,74 +239,75 @@ public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirAgainstA const string filename = "unit_test.txt"; string fullPath = Touch(di.FullName, filename, "some contents"); - Assert.Throws(() => repo.Index.Unstage(fullPath)); + Assert.Throws(() => Commands.Unstage(repo, fullPath)); } } [Fact] public void UnstagingFileWithBadParamsThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Index.Unstage(string.Empty)); - Assert.Throws(() => repo.Index.Unstage((string)null)); - Assert.Throws(() => repo.Index.Unstage(new string[] { })); - Assert.Throws(() => repo.Index.Unstage(new string[] { null })); + Assert.Throws(() => Commands.Unstage(repo, string.Empty)); + Assert.Throws(() => Commands.Unstage(repo, (string)null)); + Assert.Throws(() => Commands.Unstage(repo, Array.Empty())); + Assert.Throws(() => Commands.Unstage(repo, new string[] { null })); } } [Fact] public void CanUnstageSourceOfARename() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); - RepositoryStatus oldStatus = repo.Index.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.Nonexistent, oldStatus["branch_file.txt"].State); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Index.Unstage(new string[] { "branch_file.txt" }); + Commands.Unstage(repo, new string[] { "branch_file.txt" }); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); - Assert.Equal(FileStatus.Missing, newStatus["branch_file.txt"].State); - Assert.Equal(FileStatus.Added, newStatus["renamed_branch_file.txt"].State); + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.Empty(newStatus.RenamedInIndex); + Assert.Equal(FileStatus.DeletedFromWorkdir, newStatus["branch_file.txt"].State); + Assert.Equal(FileStatus.NewInIndex, newStatus["renamed_branch_file.txt"].State); } } [Fact] public void CanUnstageTargetOfARename() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); - RepositoryStatus oldStatus = repo.Index.RetrieveStatus(); - Assert.Equal(1, oldStatus.RenamedInIndex.Count()); + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); - repo.Index.Unstage(new string[] { "renamed_branch_file.txt" }); + Commands.Unstage(repo, new string[] { "renamed_branch_file.txt" }); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.RenamedInIndex.Count()); - Assert.Equal(FileStatus.Untracked, newStatus["renamed_branch_file.txt"].State); - Assert.Equal(FileStatus.Removed, newStatus["branch_file.txt"].State); + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.Empty(newStatus.RenamedInIndex); + Assert.Equal(FileStatus.NewInWorkdir, newStatus["renamed_branch_file.txt"].State); + Assert.Equal(FileStatus.DeletedFromIndex, newStatus["branch_file.txt"].State); } } [Fact] public void CanUnstageBothSidesOfARename() { - using (var repo = new Repository(CloneStandardTestRepo())) + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Index.Move("branch_file.txt", "renamed_branch_file.txt"); - repo.Index.Unstage(new string[] { "branch_file.txt", "renamed_branch_file.txt" }); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + Commands.Unstage(repo, new string[] { "branch_file.txt", "renamed_branch_file.txt" }); - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.Equal(FileStatus.Missing, status["branch_file.txt"].State); - Assert.Equal(FileStatus.Untracked, status["renamed_branch_file.txt"].State); + RepositoryStatus status = repo.RetrieveStatus(); + Assert.Equal(FileStatus.DeletedFromWorkdir, status["branch_file.txt"].State); + Assert.Equal(FileStatus.NewInWorkdir, status["renamed_branch_file.txt"].State); } } } diff --git a/LibGit2Sharp.Tests/WorktreeFixture.cs b/LibGit2Sharp.Tests/WorktreeFixture.cs new file mode 100644 index 000000000..224a99dbe --- /dev/null +++ b/LibGit2Sharp.Tests/WorktreeFixture.cs @@ -0,0 +1,395 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class WorktreeFixture : BaseFixture + { + [Fact] + public void RetrievingWorktreeForRandomNameReturnsNull() + { + var path = SandboxWorktreeTestRepo(); + using (var repo = new Repository(path)) + { + var worktree = repo.Worktrees["random"]; + Assert.Null(worktree); + } + } + + [Fact] + public void RetrievingWorktreeForWorktreeNameReturnsWorktree() + { + var path = SandboxWorktreeTestRepo(); + using (var repo = new Repository(path)) + { + var worktree = repo.Worktrees["logo"]; + Assert.NotNull(worktree); + } + } + + [Fact] + public void CanEnumerateRepositoryWorktrees() + { + var expectedWorktrees = new[] + { + "i-do-numbers", + "logo", + }; + + var path = SandboxWorktreeTestRepo(); + using (var repo = new Repository(path)) + { + var worktrees = repo.Worktrees.OrderBy(w => w.Name, StringComparer.Ordinal); + + Assert.Equal(expectedWorktrees, worktrees.Select(w => w.Name).ToArray()); + } + } + + [Fact] + public void CanViewLockStatusForWorktrees() + { + var testpath = SandboxWorktreeTestRepo(); + var repoPath = testpath; + using (var repo = new Repository(repoPath)) + { + // locked + var worktreeLogo = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeLogo.Name); + Assert.True(worktreeLogo.IsLocked); + Assert.Equal("Test lock reason\n", worktreeLogo.LockReason); + + // not locked + var worktreeIDoNumbers = repo.Worktrees["i-do-numbers"]; + Assert.Equal("i-do-numbers", worktreeIDoNumbers.Name); + Assert.False(worktreeIDoNumbers.IsLocked); + Assert.Null(worktreeIDoNumbers.LockReason); + } + } + + [Fact] + public void CanUnlockWorktree() + { + var testpath = SandboxWorktreeTestRepo(); + var repoPath = testpath; + using (var repo = new Repository(repoPath)) + { + // locked + var worktreeLocked = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeLocked.Name); + Assert.True(worktreeLocked.IsLocked); + Assert.Equal("Test lock reason\n", worktreeLocked.LockReason); + + worktreeLocked.Unlock(); + + // unlocked + var worktreeUnlocked = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeLocked.Name); + Assert.False(worktreeUnlocked.IsLocked); + Assert.Null(worktreeUnlocked.LockReason); + } + } + + [Fact] + public void CanLockWorktree() + { + var testpath = SandboxWorktreeTestRepo(); + var repoPath = testpath; + using (var repo = new Repository(repoPath)) + { + // unlocked + var worktreeUnlocked = repo.Worktrees["i-do-numbers"]; + Assert.Equal("i-do-numbers", worktreeUnlocked.Name); + Assert.False(worktreeUnlocked.IsLocked); + Assert.Null(worktreeUnlocked.LockReason); + + worktreeUnlocked.Lock("add a lock"); + + // locked + var worktreeLocked = repo.Worktrees["i-do-numbers"]; + Assert.Equal("i-do-numbers", worktreeLocked.Name); + Assert.True(worktreeLocked.IsLocked); + Assert.Equal("add a lock", worktreeLocked.LockReason); + } + } + + [Fact] + public void CanGetRepositoryForWorktree() + { + var testpath = SandboxWorktreeTestRepo(); + var repoPath = testpath; + using (var repo = new Repository(repoPath)) + { + var worktree = repo.Worktrees["logo"]; + + Assert.Equal("logo", worktree.Name); + using (var repository = worktree.WorktreeRepository) + { + Assert.NotNull(repository); + } + } + } + + [Fact] + public void CanPruneUnlockedWorktree() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + // unlocked + var worktreeUnlocked = repo.Worktrees["i-do-numbers"]; + Assert.Equal("i-do-numbers", worktreeUnlocked.Name); + Assert.False(worktreeUnlocked.IsLocked); + + Assert.True(repo.Worktrees.Prune(worktreeUnlocked)); + + Assert.Single(repo.Worktrees); + } + } + + [Fact] + public void CanPruneDeletedWorktree() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + var repoPath2 = repo.Info.Path; + var repoWd = repo.Info.WorkingDirectory; + // unlocked + var worktreeUnlocked = repo.Worktrees["i-do-numbers"]; + Assert.Equal("i-do-numbers", worktreeUnlocked.Name); + Assert.False(worktreeUnlocked.IsLocked); + using (var wtRepo = worktreeUnlocked.WorktreeRepository) + { + var info = wtRepo.Info; + + Directory.Delete(info.WorkingDirectory, true); + } + + Assert.True(repo.Worktrees.Prune(worktreeUnlocked)); + + Assert.Single(repo.Worktrees); + } + } + + [Fact] + public void CanNotPruneLockedWorktree() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + // locked + var worktreeUnlocked = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeUnlocked.Name); + Assert.True(worktreeUnlocked.IsLocked); + + Assert.Throws(() => repo.Worktrees.Prune(worktreeUnlocked)); + } + } + + [Fact] + public void CanUnlockThenPruneLockedWorktree() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + // locked + var worktreeLocked = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeLocked.Name); + Assert.True(worktreeLocked.IsLocked); + + worktreeLocked.Unlock(); + + repo.Worktrees.Prune(worktreeLocked); + + Assert.Single(repo.Worktrees); + } + } + + [Fact] + public void CanForcePruneLockedWorktree() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + // locked + var worktreeLocked = repo.Worktrees["logo"]; + Assert.Equal("logo", worktreeLocked.Name); + Assert.True(worktreeLocked.IsLocked); + + repo.Worktrees.Prune(worktreeLocked, true); + + Assert.Single(repo.Worktrees); + } + } + + [Fact] + public void CanAddWorktree_WithUncommitedChanges() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + var name = "blah"; + var path = Path.Combine(repo.Info.WorkingDirectory, "..", "worktrees", name); + var worktree = repo.Worktrees.Add(name, path, false); + Assert.Equal(name, worktree.Name); + Assert.False(worktree.IsLocked); + + Assert.Equal(3, repo.Worktrees.Count()); + + // Check that branch contains same number of files and folders + Assert.True(repo.RetrieveStatus().IsDirty); + var filesInMain = GetFilesOfRepo(repoPath); + var filesInBranch = GetFilesOfRepo(path); + Assert.NotEqual(filesInMain, filesInBranch); + + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Assert.False(repo.RetrieveStatus().IsDirty); + filesInMain = GetFilesOfRepo(repoPath); + filesInBranch = GetFilesOfRepo(path); + Assert.Equal(filesInMain, filesInBranch); + } + } + + [Fact] + public void CanAddWorktree_WithCommitedChanges() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + // stage all changes + Commands.Stage(repo, "*"); + repo.Commit("Apply all changes", Constants.Signature, Constants.Signature); + + Assert.Equal(2, repo.Worktrees.Count()); + + var name = "blah"; + var path = Path.Combine(repo.Info.WorkingDirectory, "..", "worktrees", name); + var worktree = repo.Worktrees.Add(name, path, false); + Assert.Equal(name, worktree.Name); + Assert.False(worktree.IsLocked); + + Assert.Equal(3, repo.Worktrees.Count()); + + // Check that branch contains same number of files and folders + Assert.False(repo.RetrieveStatus().IsDirty); + var filesInMain = GetFilesOfRepo(repoPath); + var filesInBranch = GetFilesOfRepo(path); + + Assert.Equal(filesInMain, filesInBranch); + } + } + + [Fact] + public void CanAddLockedWorktree_WithUncommitedChanges() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + var name = "blah"; + var path = Path.Combine(repo.Info.WorkingDirectory, "..", "worktrees", name); + var worktree = repo.Worktrees.Add(name, path, true); + Assert.Equal(name, worktree.Name); + Assert.True(worktree.IsLocked); + + Assert.Equal(3, repo.Worktrees.Count()); + + // Check that branch contains same number of files and folders + Assert.True(repo.RetrieveStatus().IsDirty); + var filesInMain = GetFilesOfRepo(repoPath); + var filesInBranch = GetFilesOfRepo(path); + Assert.NotEqual(filesInMain, filesInBranch); + + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Assert.False(repo.RetrieveStatus().IsDirty); + filesInMain = GetFilesOfRepo(repoPath); + filesInBranch = GetFilesOfRepo(path); + Assert.Equal(filesInMain, filesInBranch); + } + } + + [Fact] + public void CanAddLockedWorktree_WithCommitedChanges() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + // stage all changes + Commands.Stage(repo, "*"); + repo.Commit("Apply all changes", Constants.Signature, Constants.Signature); + + Assert.Equal(2, repo.Worktrees.Count()); + + var name = "blah"; + var path = Path.Combine(repo.Info.WorkingDirectory, "..", "worktrees", name); + var worktree = repo.Worktrees.Add(name, path, true); + Assert.Equal(name, worktree.Name); + Assert.True(worktree.IsLocked); + + Assert.Equal(3, repo.Worktrees.Count()); + + // Check that branch contains same number of files and folders + Assert.False(repo.RetrieveStatus().IsDirty); + var filesInMain = GetFilesOfRepo(repoPath); + var filesInBranch = GetFilesOfRepo(path); + Assert.Equal(filesInMain, filesInBranch); + } + } + + [Fact] + public void CanAddWorktreeForCommittish() + { + var repoPath = SandboxWorktreeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Assert.Equal(2, repo.Worktrees.Count()); + + var name = "blah"; + var committish = "diff-test-cases"; + var path = Path.Combine(repo.Info.WorkingDirectory, "..", "worktrees", name); + var worktree = repo.Worktrees.Add(committish, name, path, false); + Assert.Equal(name, worktree.Name); + Assert.False(worktree.IsLocked); + using (var repository = worktree.WorktreeRepository) + { + Assert.Equal(committish, repository.Head.FriendlyName); + } + Assert.Equal(3, repo.Worktrees.Count()); + + // Check that branch contains same number of files and folders + var filesInCommittish = new string[] { "numbers.txt", "super-file.txt" }; + var filesInBranch = GetFilesOfRepo(path); + Assert.Equal(filesInCommittish, filesInBranch); + } + } + + private static IEnumerable GetFilesOfRepo(string repoPath) + { + return Directory.GetFiles(repoPath, "*", SearchOption.AllDirectories) + .Where(fileName => !fileName.StartsWith(Path.Combine(repoPath, ".git"))) + .Select(fileName => fileName.Replace($"{repoPath}{Path.DirectorySeparatorChar}", "")) + .OrderBy(fileName => fileName) + .ToList(); + } + } +} diff --git a/LibGit2Sharp.Tests/ShadowCopyFixture.cs b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs similarity index 75% rename from LibGit2Sharp.Tests/ShadowCopyFixture.cs rename to LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs index c53366859..d9618c06c 100644 --- a/LibGit2Sharp.Tests/ShadowCopyFixture.cs +++ b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Reflection; using System.Security; @@ -10,14 +10,15 @@ namespace LibGit2Sharp.Tests { public class ShadowCopyFixture : BaseFixture { - [SkippableFact] + [Fact] + [Trait("TestCategory", "FailsWhileInstrumented")] public void CanProbeForNativeBinariesFromAShadowCopiedAssembly() { Type type = typeof(Wrapper); Assembly assembly = type.Assembly; // Build a new domain which will shadow copy assemblies - string cachePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + string cachePath = Path.Combine(Constants.TemporaryReposPath, Path.GetRandomFileName()); Directory.CreateDirectory(cachePath); var setup = new AppDomainSetup @@ -51,24 +52,23 @@ public void CanProbeForNativeBinariesFromAShadowCopiedAssembly() // ...but are currently loaded from different locations... string cachedAssemblyLocation = wrapper.AssemblyLocation; - if (cachedAssemblyLocation.StartsWith("/private")) - { - // On OS X, sometimes you get /private/var/… instead of /var/…, but they map to the same place. - cachedAssemblyLocation = cachedAssemblyLocation.Substring("/private".Length); - } Assert.NotEqual(sourceAssembly.Location, cachedAssemblyLocation); // ...that the assembly in the other domain is stored in the shadow copy cache... string cachedAssembliesPath = Path.Combine(setup.CachePath, setup.ApplicationName); - Assert.True(cachedAssemblyLocation.StartsWith(cachedAssembliesPath)); - - // ...that this cache doesn't contain the `NativeBinaries` folder - string cachedAssemblyParentPath = Path.GetDirectoryName(cachedAssemblyLocation); - Assert.False(Directory.Exists(Path.Combine(cachedAssemblyParentPath, "NativeBinaries"))); + Assert.StartsWith(cachedAssembliesPath, cachedAssemblyLocation); - // ...whereas `NativeBinaries` of course exists next to the source assembly - string sourceAssemblyParentPath = Path.GetDirectoryName(new Uri(sourceAssembly.EscapedCodeBase).LocalPath); - Assert.True(Directory.Exists(Path.Combine(sourceAssemblyParentPath, "NativeBinaries"))); + if (!Constants.IsRunningOnUnix) + { + // ...that this cache doesn't contain the `lib` folder + string cachedAssemblyParentPath = Path.GetDirectoryName(cachedAssemblyLocation); + Assert.False(Directory.Exists(Path.Combine(cachedAssemblyParentPath, "lib"))); + + // ...whereas `lib` of course exists next to the source assembly + string sourceAssemblyParentPath = + Path.GetDirectoryName(new Uri(sourceAssembly.EscapedCodeBase).LocalPath); + Assert.True(Directory.Exists(Path.Combine(sourceAssemblyParentPath, "lib"))); + } AppDomain.Unload(domain); } diff --git a/LibGit2Sharp.Tests/SmartSubtransportFixture.cs b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs similarity index 68% rename from LibGit2Sharp.Tests/SmartSubtransportFixture.cs rename to LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs index 4dbbd9b0f..4e3b03ce3 100644 --- a/LibGit2Sharp.Tests/SmartSubtransportFixture.cs +++ b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Net; using System.Net.Security; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -39,9 +37,9 @@ public void CustomSmartSubtransportTest(string scheme, string url) registration = GlobalSettings.RegisterSmartSubtransport(scheme); Assert.NotNull(registration); - using (var repo = new Repository(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(remoteName, url); // Set up structures for the expected results // and verifying the RemoteUpdateTips callback. @@ -55,7 +53,7 @@ public void CustomSmartSubtransportTest(string scheme, string url) } // Add the expected tags - string[] expectedTagNames = { "blob", "commit_tree" }; + string[] expectedTagNames = { "blob", "commit_tree", "annotated_tag" }; foreach (string tagName in expectedTagNames) { TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName]; @@ -63,7 +61,9 @@ public void CustomSmartSubtransportTest(string scheme, string url) } // Perform the actual fetch - repo.Network.Fetch(remote, new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto }); + Commands.Fetch(repo, remoteName, Array.Empty(), + new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto }, + null); // Verify the expected expectedFetchState.CheckUpdatedReferences(repo); @@ -77,6 +77,59 @@ public void CustomSmartSubtransportTest(string scheme, string url) } } + //[Theory] + //[InlineData("https", "https://bitbucket.org/libgit2/testgitrepository.git", "libgit3", "libgit3")] + //public void CanUseCredentials(string scheme, string url, string user, string pass) + //{ + // string remoteName = "testRemote"; + + // var scd = BuildSelfCleaningDirectory(); + // Repository.Init(scd.RootedDirectoryPath); + + // SmartSubtransportRegistration registration = null; + + // try + // { + // // Disable server certificate validation for testing. + // // Do *NOT* enable this in production. + // ServicePointManager.ServerCertificateValidationCallback = certificateValidationCallback; + + // registration = GlobalSettings.RegisterSmartSubtransport(scheme); + // Assert.NotNull(registration); + + // using (var repo = new Repository(scd.DirectoryPath)) + // { + // repo.Network.Remotes.Add(remoteName, url); + + // // Set up structures for the expected results + // // and verifying the RemoteUpdateTips callback. + // TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance; + // ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName); + + // // Add expected branch objects + // foreach (KeyValuePair kvp in expectedResults.BranchTips) + // { + // expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value); + // } + + // // Perform the actual fetch + // Commands.Fetch(repo, remoteName, new string[0], new FetchOptions { + // OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto, + // CredentialsProvider = (_user, _valid, _hostname) => new UsernamePasswordCredentials() { Username = user, Password = pass }, + // }, null); + + // // Verify the expected + // expectedFetchState.CheckUpdatedReferences(repo); + // } + // } + // finally + // { + // GlobalSettings.UnregisterSmartSubtransport(registration); + + // ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback; + // } + //} + [Fact] public void CannotReregisterScheme() { @@ -108,29 +161,29 @@ public void CannotUnregisterTwice() private class MockSmartSubtransport : RpcSmartSubtransport { - protected override SmartSubtransportStream Action(String url, GitSmartSubtransportAction action) + protected override SmartSubtransportStream Action(string url, GitSmartSubtransportAction action) { - String endpointUrl, contentType = null; + string endpointUrl, contentType = null; bool isPost = false; switch (action) { case GitSmartSubtransportAction.UploadPackList: - endpointUrl = String.Concat(url, "/info/refs?service=git-upload-pack"); + endpointUrl = string.Concat(url, "/info/refs?service=git-upload-pack"); break; case GitSmartSubtransportAction.UploadPack: - endpointUrl = String.Concat(url, "/git-upload-pack"); + endpointUrl = string.Concat(url, "/git-upload-pack"); contentType = "application/x-git-upload-pack-request"; isPost = true; break; case GitSmartSubtransportAction.ReceivePackList: - endpointUrl = String.Concat(url, "/info/refs?service=git-receive-pack"); + endpointUrl = string.Concat(url, "/info/refs?service=git-receive-pack"); break; case GitSmartSubtransportAction.ReceivePack: - endpointUrl = String.Concat(url, "/git-receive-pack"); + endpointUrl = string.Concat(url, "/git-receive-pack"); contentType = "application/x-git-receive-pack-request"; isPost = true; break; @@ -204,6 +257,8 @@ public override int Write(Stream dataStream, long length) private static HttpWebRequest CreateWebRequest(string endpointUrl, bool isPost, string contentType) { + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; + HttpWebRequest webRequest = (HttpWebRequest)HttpWebRequest.Create(endpointUrl); webRequest.UserAgent = "git/1.0 (libgit2 custom transport)"; webRequest.ServicePoint.Expect100Continue = false; @@ -235,7 +290,31 @@ private HttpWebResponse GetResponseWithRedirects() } } - response = (HttpWebResponse)request.GetResponse(); + try + { + response = (HttpWebResponse)request.GetResponse(); + } + catch (WebException ex) + { + response = ex.Response as HttpWebResponse; + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + Credentials cred; + int ret = SmartTransport.AcquireCredentials(out cred, null, typeof(UsernamePasswordCredentials)); + if (ret != 0) + { + throw new InvalidOperationException("dunno"); + } + + request = CreateWebRequest(EndpointUrl, IsPost, ContentType); + UsernamePasswordCredentials userpass = (UsernamePasswordCredentials)cred; + request.Credentials = new NetworkCredential(userpass.Username, userpass.Password); + continue; + } + + // rethrow if it's not 401 + throw; + } if (response.StatusCode == HttpStatusCode.Moved || response.StatusCode == HttpStatusCode.Redirect) { @@ -243,6 +322,7 @@ private HttpWebResponse GetResponseWithRedirects() continue; } + break; } diff --git a/LibGit2Sharp.Tests/xunit.runner.json b/LibGit2Sharp.Tests/xunit.runner.json new file mode 100644 index 000000000..e54567a36 --- /dev/null +++ b/LibGit2Sharp.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.github.io/schema/current/xunit.runner.schema.json", + "shadowCopy": false +} diff --git a/LibGit2Sharp.sln b/LibGit2Sharp.sln index 8d9f849eb..e99eec26f 100644 --- a/LibGit2Sharp.sln +++ b/LibGit2Sharp.sln @@ -1,57 +1,51 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibGit2Sharp", "LibGit2Sharp\LibGit2Sharp.csproj", "{EE6ED99F-CB12-4683-B055-D28FC7357A34}" +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.33516.290 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibGit2Sharp", "LibGit2Sharp\LibGit2Sharp.csproj", "{EE6ED99F-CB12-4683-B055-D28FC7357A34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibGit2Sharp.Tests", "LibGit2Sharp.Tests\LibGit2Sharp.Tests.csproj", "{286E63EB-04DD-4ADE-88D6-041B57800761}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibGit2Sharp.Tests", "LibGit2Sharp.Tests\LibGit2Sharp.Tests.csproj", "{286E63EB-04DD-4ADE-88D6-041B57800761}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0CA739FD-DA4D-4F64-9834-DA14A3ECD04B}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + Targets\CodeGenerator.targets = Targets\CodeGenerator.targets + Directory.Build.props = Directory.Build.props + Targets\GenerateNativeDllName.targets = Targets\GenerateNativeDllName.targets + nuget.config = nuget.config + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeLibraryLoadTestApp.x86", "NativeLibraryLoadTestApp\x86\NativeLibraryLoadTestApp.x86.csproj", "{86453D2C-4953-4DF4-B12A-ADE579608BAA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeLibraryLoadTestApp.x64", "NativeLibraryLoadTestApp\x64\NativeLibraryLoadTestApp.x64.csproj", "{5C55175D-6A1F-4C51-B791-BF7DD00124EE}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Leaks|Any CPU = Leaks|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Leaks|Any CPU.ActiveCfg = Leaks|Any CPU - {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Leaks|Any CPU.Build.0 = Leaks|Any CPU {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|Any CPU.ActiveCfg = Release|Any CPU {EE6ED99F-CB12-4683-B055-D28FC7357A34}.Release|Any CPU.Build.0 = Release|Any CPU {286E63EB-04DD-4ADE-88D6-041B57800761}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {286E63EB-04DD-4ADE-88D6-041B57800761}.Debug|Any CPU.Build.0 = Debug|Any CPU - {286E63EB-04DD-4ADE-88D6-041B57800761}.Leaks|Any CPU.ActiveCfg = Leaks|Any CPU - {286E63EB-04DD-4ADE-88D6-041B57800761}.Leaks|Any CPU.Build.0 = Leaks|Any CPU {286E63EB-04DD-4ADE-88D6-041B57800761}.Release|Any CPU.ActiveCfg = Release|Any CPU {286E63EB-04DD-4ADE-88D6-041B57800761}.Release|Any CPU.Build.0 = Release|Any CPU + {86453D2C-4953-4DF4-B12A-ADE579608BAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86453D2C-4953-4DF4-B12A-ADE579608BAA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86453D2C-4953-4DF4-B12A-ADE579608BAA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86453D2C-4953-4DF4-B12A-ADE579608BAA}.Release|Any CPU.Build.0 = Release|Any CPU + {5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5C55175D-6A1F-4C51-B791-BF7DD00124EE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = LibGit2Sharp.Core.Generator\LibGit2Sharp.Core.Generator.csproj - Policies = $0 - $0.TextStylePolicy = $1 - $1.inheritsSet = null - $1.scope = text/x-csharp - $0.CSharpFormattingPolicy = $2 - $2.IndentSwitchBody = True - $2.BeforeMethodCallParentheses = False - $2.BeforeMethodDeclarationParentheses = False - $2.BeforeConstructorDeclarationParentheses = False - $2.BeforeDelegateDeclarationParentheses = False - $2.NewParentheses = False - $2.inheritsSet = Mono - $2.inheritsScope = text/x-csharp - $2.scope = text/x-csharp - $0.StandardHeader = $3 - $3.Text = - $3.inheritsSet = Apache2License - $0.TextStylePolicy = $4 - $4.FileWidth = 120 - $4.RemoveTrailingWhitespace = True - $4.inheritsSet = VisualStudio - $4.inheritsScope = text/plain - $4.scope = text/plain + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9BD5F77D-E47D-4621-9AA0-8598766902B9} EndGlobalSection EndGlobal diff --git a/LibGit2Sharp.sln.DotSettings b/LibGit2Sharp.sln.DotSettings deleted file mode 100644 index 3d1a941ea..000000000 --- a/LibGit2Sharp.sln.DotSettings +++ /dev/null @@ -1,13 +0,0 @@ - - <?xml version="1.0" encoding="utf-16"?><Profile name="LibGit2Sharp"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode></Profile> - TOGETHER - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - True - False - True - True - diff --git a/LibGit2Sharp.v2.ncrunchsolution b/LibGit2Sharp.v2.ncrunchsolution deleted file mode 100644 index bf2ebc9c1..000000000 --- a/LibGit2Sharp.v2.ncrunchsolution +++ /dev/null @@ -1,13 +0,0 @@ - - 1 - false - false - false - UseDynamicAnalysis - UseStaticAnalysis - UseStaticAnalysis - UseStaticAnalysis - UseStaticAnalysis - - - \ No newline at end of file diff --git a/LibGit2Sharp/AfterRebaseStepInfo.cs b/LibGit2Sharp/AfterRebaseStepInfo.cs new file mode 100644 index 000000000..54558b59d --- /dev/null +++ b/LibGit2Sharp/AfterRebaseStepInfo.cs @@ -0,0 +1,61 @@ +namespace LibGit2Sharp +{ + /// + /// Information about a rebase step that was just completed. + /// + public class AfterRebaseStepInfo + { + /// + /// Needed for mocking. + /// + protected AfterRebaseStepInfo() + { } + + internal AfterRebaseStepInfo(RebaseStepInfo stepInfo, Commit commit, long completedStepIndex, long totalStepCount) + { + StepInfo = stepInfo; + Commit = commit; + WasPatchAlreadyApplied = false; + CompletedStepIndex = completedStepIndex; + TotalStepCount = totalStepCount; + } + + /// + /// Constructor to call when the patch has already been applied for this step. + /// + /// + /// + /// + internal AfterRebaseStepInfo(RebaseStepInfo stepInfo, long completedStepIndex, long totalStepCount) + : this(stepInfo, null, completedStepIndex, totalStepCount) + { + WasPatchAlreadyApplied = true; + } + + /// + /// The info on the completed step. + /// + public virtual RebaseStepInfo StepInfo { get; private set; } + + /// + /// The commit generated by the step, if any. + /// + public virtual Commit Commit { get; private set; } + + /// + /// Was the changes for this step already applied. If so, + /// will be null. + /// + public virtual bool WasPatchAlreadyApplied { get; private set; } + + /// + /// The index of the step that was just completed. + /// + public virtual long CompletedStepIndex { get; private set; } + + /// + /// The total number of steps in the rebase operation. + /// + public virtual long TotalStepCount { get; private set; } + } +} diff --git a/LibGit2Sharp/AmbiguousSpecificationException.cs b/LibGit2Sharp/AmbiguousSpecificationException.cs index 3b9024cf8..b5ddd7963 100644 --- a/LibGit2Sharp/AmbiguousSpecificationException.cs +++ b/LibGit2Sharp/AmbiguousSpecificationException.cs @@ -1,20 +1,24 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// The exception that is thrown when the provided specification cannot uniquely identify a reference, an object or a path. /// +#if NETFRAMEWORK [Serializable] - public class AmbiguousSpecificationException : LibGit2SharpException +#endif + public class AmbiguousSpecificationException : NativeException { /// /// Initializes a new instance of the class. /// public AmbiguousSpecificationException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -22,6 +26,15 @@ public AmbiguousSpecificationException() /// A message that describes the error. public AmbiguousSpecificationException(string message) : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public AmbiguousSpecificationException(string format, params object[] args) + : base(string.Format(format, args)) { } @@ -32,9 +45,9 @@ public AmbiguousSpecificationException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public AmbiguousSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -42,7 +55,15 @@ public AmbiguousSpecificationException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected AmbiguousSpecificationException(SerializationInfo info, StreamingContext context) : base(info, context) + { } +#endif + + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.Ambiguous; + } } } } diff --git a/LibGit2Sharp/BareRepositoryException.cs b/LibGit2Sharp/BareRepositoryException.cs index 00b61a04b..412e5e4d4 100644 --- a/LibGit2Sharp/BareRepositoryException.cs +++ b/LibGit2Sharp/BareRepositoryException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -8,15 +10,16 @@ namespace LibGit2Sharp /// The exception that is thrown when an operation which requires a /// working directory is performed against a bare repository. /// +#if NETFRAMEWORK [Serializable] - public class BareRepositoryException : LibGit2SharpException +#endif + public class BareRepositoryException : NativeException { /// /// Initializes a new instance of the class. /// public BareRepositoryException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +27,16 @@ public BareRepositoryException() /// A message that describes the error. public BareRepositoryException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public BareRepositoryException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,9 +45,9 @@ public BareRepositoryException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public BareRepositoryException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -44,12 +55,19 @@ public BareRepositoryException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected BareRepositoryException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal BareRepositoryException(string message, GitErrorCategory category) + : base(message, category) + { } - internal BareRepositoryException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.BareRepo; + } } } } diff --git a/LibGit2Sharp/BeforeRebaseStepInfo.cs b/LibGit2Sharp/BeforeRebaseStepInfo.cs new file mode 100644 index 000000000..e01175c08 --- /dev/null +++ b/LibGit2Sharp/BeforeRebaseStepInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Information about a rebase step that is about to be performed. + /// + public class BeforeRebaseStepInfo + { + /// + /// Needed for mocking. + /// + protected BeforeRebaseStepInfo() + { } + + internal BeforeRebaseStepInfo(RebaseStepInfo stepInfo, long stepIndex, long totalStepCount) + { + StepInfo = stepInfo; + StepIndex = stepIndex; + TotalStepCount = totalStepCount; + } + + /// + /// Information on the step that is about to be performed. + /// + public virtual RebaseStepInfo StepInfo { get; private set; } + + /// + /// The index of the step that is to be run. + /// + public virtual long StepIndex { get; private set; } + + /// + /// The total number of steps in the rebase operation. + /// + public virtual long TotalStepCount { get; private set; } + } +} diff --git a/LibGit2Sharp/BlameHunk.cs b/LibGit2Sharp/BlameHunk.cs index 79dbf3945..6350a9bbc 100644 --- a/LibGit2Sharp/BlameHunk.cs +++ b/LibGit2Sharp/BlameHunk.cs @@ -19,37 +19,43 @@ public class BlameHunk : IEquatable x => x.InitialSignature, x => x.InitialCommit); - - internal BlameHunk(IRepository repository, GitBlameHunk rawHunk) + internal unsafe BlameHunk(IRepository repository, git_blame_hunk* rawHunk) { - finalCommit = new Lazy(() => repository.Lookup(rawHunk.FinalCommitId)); - origCommit = new Lazy(() => repository.Lookup(rawHunk.OrigCommitId)); + var origId = ObjectId.BuildFromPtr(&rawHunk->orig_commit_id); + var finalId = ObjectId.BuildFromPtr(&rawHunk->final_commit_id); + + finalCommit = new Lazy(() => repository.Lookup(finalId)); + origCommit = new Lazy(() => repository.Lookup(origId)); + - if (rawHunk.OrigPath != IntPtr.Zero) + if (rawHunk->orig_path != null) { - InitialPath = LaxUtf8Marshaler.FromNative(rawHunk.OrigPath); + InitialPath = LaxUtf8Marshaler.FromNative(rawHunk->orig_path); } - LineCount = rawHunk.LinesInHunk; + + LineCount = (int)rawHunk->lines_in_hunk.ToUInt32(); // Libgit2's line numbers are 1-based - FinalStartLineNumber = rawHunk.FinalStartLineNumber - 1; - InitialStartLineNumber = rawHunk.OrigStartLineNumber - 1; + FinalStartLineNumber = (int)rawHunk->final_start_line_number.ToUInt32() - 1; + InitialStartLineNumber = (int)rawHunk->orig_start_line_number.ToUInt32() - 1; // Signature objects need to have ownership of their native pointers - if (rawHunk.FinalSignature != IntPtr.Zero) + if (rawHunk->final_signature != null) { - FinalSignature = new Signature(Proxy.git_signature_dup(rawHunk.FinalSignature)); + FinalSignature = new Signature(rawHunk->final_signature); } - if (rawHunk.OrigSignature != IntPtr.Zero) + + if (rawHunk->orig_signature != null) { - InitialSignature = new Signature(Proxy.git_signature_dup(rawHunk.OrigSignature)); + InitialSignature = new Signature(rawHunk->orig_signature); } } /// /// For easier mocking /// - protected BlameHunk() { } + protected BlameHunk() + { } /// /// Determine if this hunk contains a given line. @@ -108,8 +114,8 @@ private string DebuggerDisplay return string.Format(CultureInfo.InvariantCulture, "{0}-{1} ({2})", FinalStartLineNumber, - FinalStartLineNumber+LineCount-1, - FinalCommit.ToString().Substring(0,7)); + FinalStartLineNumber + LineCount - 1, + FinalCommit.ToString().Substring(0, 7)); } } @@ -129,10 +135,10 @@ public bool Equals(BlameHunk other) } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as BlameHunk); diff --git a/LibGit2Sharp/BlameHunkCollection.cs b/LibGit2Sharp/BlameHunkCollection.cs index f487915cd..2766ee7a6 100644 --- a/LibGit2Sharp/BlameHunkCollection.cs +++ b/LibGit2Sharp/BlameHunkCollection.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -20,24 +21,32 @@ public class BlameHunkCollection : IEnumerable /// protected BlameHunkCollection() { } - internal BlameHunkCollection(Repository repo, RepositorySafeHandle repoHandle, string path, BlameOptions options) + internal unsafe BlameHunkCollection(Repository repo, RepositoryHandle repoHandle, string path, BlameOptions options) { this.repo = repo; - var rawopts = new GitBlameOptions + var rawopts = new git_blame_options { version = 1, flags = options.Strategy.ToGitBlameOptionFlags(), - MinLine = (uint)options.MinLine, - MaxLine = (uint)options.MaxLine, + min_line = new UIntPtr((uint)options.MinLine), + max_line = new UIntPtr((uint)options.MaxLine), }; + if (options.StartingAt != null) { - rawopts.NewestCommit = repo.Committish(options.StartingAt).Oid; + fixed (byte* p = rawopts.newest_commit.Id) + { + Marshal.Copy(repo.Committish(options.StartingAt).Oid.Id, 0, new IntPtr(p), git_oid.Size); + } } + if (options.StoppingAt != null) { - rawopts.OldestCommit = repo.Committish(options.StoppingAt).Oid; + fixed (byte* p = rawopts.oldest_commit.Id) + { + Marshal.Copy(repo.Committish(options.StoppingAt).Oid.Id, 0, new IntPtr(p), git_oid.Size); + } } using (var blameHandle = Proxy.git_blame_file(repoHandle, path, rawopts)) @@ -73,7 +82,7 @@ public virtual BlameHunk HunkForLine(int line) { return hunk; } - throw new ArgumentOutOfRangeException("line", "No hunk for that line"); + throw new ArgumentOutOfRangeException(nameof(line), "No hunk for that line"); } /// diff --git a/LibGit2Sharp/BlameOptions.cs b/LibGit2Sharp/BlameOptions.cs index 1fa41a1d2..c39a3f536 100644 --- a/LibGit2Sharp/BlameOptions.cs +++ b/LibGit2Sharp/BlameOptions.cs @@ -1,6 +1,4 @@ -using System; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Strategy used for blaming. diff --git a/LibGit2Sharp/Blob.cs b/LibGit2Sharp/Blob.cs index bf1a72ac2..29ef8d812 100644 --- a/LibGit2Sharp/Blob.cs +++ b/LibGit2Sharp/Blob.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,9 +8,12 @@ namespace LibGit2Sharp /// /// Stores the binary content of a tracked file. /// + /// + /// Since the introduction of partially cloned repositories, blobs might be missing on your local repository (see https://git-scm.com/docs/partial-clone) + /// public class Blob : GitObject { - private readonly ILazy lazySize; + private readonly ILazy lazySize; private readonly ILazy lazyIsBinary; /// @@ -21,23 +25,30 @@ protected Blob() internal Blob(Repository repo, ObjectId id) : base(repo, id) { - lazySize = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_blob_rawsize); - lazyIsBinary = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_blob_is_binary); + lazySize = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_blob_rawsize, throwIfMissing: true); + lazyIsBinary = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_blob_is_binary, throwIfMissing: true); } /// /// Gets the size in bytes of the raw content of a blob. + /// Please note that this would load entire blob content in the memory to compute the Size. + /// In order to read blob size from header, Repository.ObjectDatabase.RetrieveObjectMetadata(Blob.Id).Size + /// can be used. + /// /// - public virtual int Size { get { return (int)lazySize.Value; } } + /// Throws if blob is missing + public virtual long Size => lazySize.Value; /// /// Determine if the blob content is most certainly binary or not. /// - public virtual bool IsBinary { get { return lazyIsBinary.Value; } } + /// Throws if blob is missing + public virtual bool IsBinary => lazyIsBinary.Value; /// /// Gets the blob content in a . /// + /// Throws if blob is missing public virtual Stream GetContentStream() { return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size); @@ -48,10 +59,72 @@ public virtual Stream GetContentStream() /// checked out to the working directory. /// Parameter controlling content filtering behavior /// + /// Throws if blob is missing public virtual Stream GetContentStream(FilteringOptions filteringOptions) { Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); + return Proxy.git_blob_filtered_content_stream(repo.Handle, Id, filteringOptions.HintPath, false); } + + /// + /// Gets the blob content, decoded with UTF8 encoding if the encoding cannot be detected from the byte order mark + /// + /// Blob content as text. + /// Throws if blob is missing + public virtual string GetContentText() + { + return ReadToEnd(GetContentStream(), null); + } + + /// + /// Gets the blob content decoded with the specified encoding, + /// or according to byte order marks, or the specified encoding as a fallback + /// + /// The encoding of the text to use, if it cannot be detected + /// Blob content as text. + /// Throws if blob is missing + public virtual string GetContentText(Encoding encoding) + { + Ensure.ArgumentNotNull(encoding, "encoding"); + + return ReadToEnd(GetContentStream(), encoding); + } + + /// + /// Gets the blob content, decoded with UTF8 encoding if the encoding cannot be detected + /// + /// Parameter controlling content filtering behavior + /// Blob content as text. + /// Throws if blob is missing + public virtual string GetContentText(FilteringOptions filteringOptions) + { + return GetContentText(filteringOptions, null); + } + + /// + /// Gets the blob content as it would be checked out to the + /// working directory, decoded with the specified encoding, + /// or according to byte order marks, with UTF8 as fallback, + /// if is null. + /// + /// Parameter controlling content filtering behavior + /// The encoding of the text. (default: detected or UTF8) + /// Blob content as text. + /// Throws if blob is missing + public virtual string GetContentText(FilteringOptions filteringOptions, Encoding encoding) + { + Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); + + return ReadToEnd(GetContentStream(filteringOptions), encoding); + } + + private static string ReadToEnd(Stream stream, Encoding encoding) + { + using (var reader = new StreamReader(stream, encoding ?? LaxUtf8Marshaler.Encoding, encoding == null)) + { + return reader.ReadToEnd(); + } + } } } diff --git a/LibGit2Sharp/BlobExtensions.cs b/LibGit2Sharp/BlobExtensions.cs deleted file mode 100644 index c9e15bf4c..000000000 --- a/LibGit2Sharp/BlobExtensions.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.IO; -using System.Text; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class BlobExtensions - { - /// - /// Gets the blob content decoded with the specified encoding, - /// or according to byte order marks, with UTF8 as fallback, - /// if is null. - /// - /// The blob for which the content will be returned. - /// The encoding of the text. (default: detected or UTF8) - /// Blob content as text. - public static string GetContentText(this Blob blob, Encoding encoding = null) - { - Ensure.ArgumentNotNull(blob, "blob"); - - using (var reader = new StreamReader(blob.GetContentStream(), encoding ?? Encoding.UTF8, encoding == null)) - { - return reader.ReadToEnd(); - } - } - - /// - /// Gets the blob content as it would be checked out to the - /// working directory, decoded with the specified encoding, - /// or according to byte order marks, with UTF8 as fallback, - /// if is null. - /// - /// The blob for which the content will be returned. - /// Parameter controlling content filtering behavior - /// The encoding of the text. (default: detected or UTF8) - /// Blob content as text. - public static string GetContentText(this Blob blob, FilteringOptions filteringOptions, Encoding encoding = null) - { - Ensure.ArgumentNotNull(blob, "blob"); - Ensure.ArgumentNotNull(filteringOptions, "filteringOptions"); - - using (var reader = new StreamReader(blob.GetContentStream(filteringOptions), encoding ?? Encoding.UTF8, encoding == null)) - { - return reader.ReadToEnd(); - } - } - } -} diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index 0067bebb3..807456688 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -26,8 +25,7 @@ protected Branch() /// The full name of the reference internal Branch(Repository repo, Reference reference, string canonicalName) : this(repo, reference, _ => canonicalName) - { - } + { } /// /// Initializes a new instance of an orphaned class. @@ -39,8 +37,7 @@ internal Branch(Repository repo, Reference reference, string canonicalName) /// The reference. internal Branch(Repository repo, Reference reference) : this(repo, reference, r => r.TargetIdentifier) - { - } + { } private Branch(Repository repo, Reference reference, Func canonicalNameSelector) : base(repo, reference, canonicalNameSelector) @@ -109,7 +106,15 @@ public virtual BranchTrackingDetails TrackingDetails /// public virtual bool IsCurrentRepositoryHead { - get { return repo.Head == this; } + get + { + if (this is DetachedHead) + { + return repo.Head.Reference.TargetIdentifier == this.Reference.TargetIdentifier; + } + + return repo.Head.Reference.TargetIdentifier == this.CanonicalName; + } } /// @@ -125,7 +130,7 @@ public virtual Commit Tip /// public virtual ICommitLog Commits { - get { return repo.Commits.QueryBy(new CommitFilter { Since = this }); } + get { return repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = this }); } } /// @@ -141,7 +146,10 @@ public virtual string UpstreamBranchCanonicalName { if (IsRemote) { - return Remote.FetchSpecTransformToSource(CanonicalName); + using (var remote = repo.Network.Remotes.RemoteForName(RemoteName)) + { + return remote.FetchSpecTransformToSource(CanonicalName); + } } return UpstreamBranchCanonicalNameFromLocalBranch(); @@ -149,41 +157,28 @@ public virtual string UpstreamBranchCanonicalName } /// - /// Get the remote for the branch. + /// Get the name of the remote for the branch. /// /// If this is a local branch, this will return the configured /// to fetch from and push to. If this is a - /// remote-tracking branch, this will return the remote containing - /// the tracked branch. + /// remote-tracking branch, this will return the name of the remote + /// containing the tracked branch. If there is no tracking information, + /// this will return null. /// /// - public virtual Remote Remote + public virtual string RemoteName { get { - string remoteName; - - if (IsRemote) - { - remoteName = RemoteNameFromRemoteTrackingBranch(); - } - else - { - remoteName = RemoteNameFromLocalBranch(); - } - - if (remoteName == null) - { - return null; - } - - return repo.Network.Remotes[remoteName]; + return IsRemote + ? RemoteNameFromRemoteTrackingBranch() + : RemoteNameFromLocalBranch(); } } private string UpstreamBranchCanonicalNameFromLocalBranch() { - ConfigurationEntry mergeRefEntry = repo.Config.Get("branch", Name, "merge"); + ConfigurationEntry mergeRefEntry = repo.Config.Get("branch", FriendlyName, "merge"); if (mergeRefEntry == null) { @@ -195,7 +190,7 @@ private string UpstreamBranchCanonicalNameFromLocalBranch() private string RemoteNameFromLocalBranch() { - ConfigurationEntry remoteEntry = repo.Config.Get("branch", Name, "remote"); + ConfigurationEntry remoteEntry = repo.Config.Get("branch", FriendlyName, "remote"); if (remoteEntry == null) { @@ -219,32 +214,6 @@ private string RemoteNameFromRemoteTrackingBranch() return Proxy.git_branch_remote_name(repo.Handle, CanonicalName, false); } - /// - /// Checkout the tip commit of this object. - /// If this commit is the current tip of the branch, will checkout - /// the named branch. Otherwise, will checkout the tip commit as a - /// detached HEAD. - /// - public virtual void Checkout() - { - repo.Checkout(this); - } - - /// - /// Checkout the tip commit of this object with - /// parameter specifying checkout - /// behavior. If this commit is the current tip of the branch, will - /// checkout the named branch. Otherwise, will checkout the tip - /// commit as a detached HEAD. - /// - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - public virtual void Checkout(CheckoutOptions options, Signature signature = null) - { - Ensure.ArgumentNotNull(options, "options"); - repo.Checkout(this, options, signature); - } - private Branch ResolveTrackedBranch() { if (IsRemote) @@ -291,9 +260,9 @@ protected override string Shorten() return CanonicalName.Substring(Reference.RemoteTrackingBranchPrefix.Length); } - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, - "'{0}' does not look like a valid branch name.", CanonicalName)); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "'{0}' does not look like a valid branch name.", + CanonicalName)); } } } diff --git a/LibGit2Sharp/BranchCollection.cs b/LibGit2Sharp/BranchCollection.cs index 929661fea..d81a48177 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -92,7 +92,8 @@ private Branch BuildFromReferenceName(string canonicalName) public virtual IEnumerator GetEnumerator() { return Proxy.git_branch_iterator(repo, GitBranchType.GIT_BRANCH_ALL) - .ToList().GetEnumerator(); + .ToList() + .GetEnumerator(); } /// @@ -106,43 +107,90 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Create a new local branch with the specified name + /// + /// The name of the branch. + /// Revparse spec for the target commit. + /// A new . + public virtual Branch Add(string name, string committish) + { + return Add(name, committish, false); + } + + /// + /// Create a new local branch with the specified name + /// + /// The name of the branch. + /// The target commit. + /// A new . + public virtual Branch Add(string name, Commit commit) + { + return Add(name, commit, false); + } + /// /// Create a new local branch with the specified name /// /// The name of the branch. /// The target commit. - /// Identity used for updating the reflog - /// Message added to the reflog. If null, the default is "branch: Created from [sha]". /// True to allow silent overwriting a potentially existing branch, false otherwise. /// A new . - public virtual Branch Add(string name, Commit commit, Signature signature, string logMessage = null, bool allowOverwrite = false) + public virtual Branch Add(string name, Commit commit, bool allowOverwrite) { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(commit, "commit"); - if (logMessage == null) - { - logMessage = "branch: Created from " + commit.Id; - } + return Add(name, commit.Sha, allowOverwrite); + } + + /// + /// Create a new local branch with the specified name + /// + /// The name of the branch. + /// Revparse spec for the target commit. + /// True to allow silent overwriting a potentially existing branch, false otherwise. + /// A new . + public virtual Branch Add(string name, string committish, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); - using (Proxy.git_branch_create(repo.Handle, name, commit.Id, allowOverwrite, signature.OrDefault(repo.Config), logMessage)) {} + using (Proxy.git_branch_create_from_annotated(repo.Handle, name, committish, allowOverwrite)) + { } var branch = this[ShortToLocalName(name)]; return branch; } /// - /// Create a new local branch with the specified name, using the default reflog message + /// Deletes the branch with the specified name. /// - /// The name of the branch. - /// The target commit. - /// True to allow silent overwriting a potentially existing branch, false otherwise. - /// A new . - public virtual Branch Add(string name, Commit commit, bool allowOverwrite = false) + /// The name of the branch to delete. + public virtual void Remove(string name) { - return Add(name, commit, null, null, allowOverwrite); + Remove(name, false); } + /// + /// Deletes the branch with the specified name. + /// + /// The name of the branch to delete. + /// True if the provided is the name of a remote branch, false otherwise. + public virtual void Remove(string name, bool isRemote) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + string branchName = isRemote ? Reference.RemoteTrackingBranchPrefix + name : name; + + Branch branch = this[branchName]; + + if (branch == null) + { + return; + } + + Remove(branch); + } /// /// Deletes the specified branch. /// @@ -151,60 +199,82 @@ public virtual void Remove(Branch branch) { Ensure.ArgumentNotNull(branch, "branch"); - using (ReferenceSafeHandle referencePtr = repo.Refs.RetrieveReferencePtr(branch.CanonicalName)) + using (ReferenceHandle referencePtr = repo.Refs.RetrieveReferencePtr(branch.CanonicalName)) { Proxy.git_branch_delete(referencePtr); } } /// - /// Rename an existing local branch + /// Rename an existing local branch, using the default reflog message /// - /// The current local branch. + /// The current branch name. + /// The new name the existing branch should bear. + /// A new . + public virtual Branch Rename(string currentName, string newName) + { + return Rename(currentName, newName, false); + } + + /// + /// Rename an existing local branch, using the default reflog message + /// + /// The current branch name. /// The new name the existing branch should bear. - /// Identity used for updating the reflog - /// Message added to the reflog. If null, the default is "branch: renamed [old] to [new]". /// True to allow silent overwriting a potentially existing branch, false otherwise. /// A new . - public virtual Branch Rename(Branch branch, string newName, Signature signature, string logMessage = null, bool allowOverwrite = false) + public virtual Branch Rename(string currentName, string newName, bool allowOverwrite) { - Ensure.ArgumentNotNull(branch, "branch"); + Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); - if (branch.IsRemote) - { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, - "Cannot rename branch '{0}'. It's a remote tracking branch.", branch.Name)); - } + Branch branch = this[currentName]; - if (logMessage == null) + if (branch == null) { - logMessage = string.Format(CultureInfo.InvariantCulture, - "branch: renamed {0} to {1}", branch.CanonicalName, Reference.LocalBranchPrefix + newName); + throw new LibGit2SharpException("No branch named '{0}' exists in the repository."); } - using (ReferenceSafeHandle referencePtr = repo.Refs.RetrieveReferencePtr(Reference.LocalBranchPrefix + branch.Name)) - { - using (Proxy.git_branch_move(referencePtr, newName, allowOverwrite, signature.OrDefault(repo.Config), logMessage)) - { - } - } + return Rename(branch, newName, allowOverwrite); + } - var newBranch = this[newName]; - return newBranch; + /// + /// Rename an existing local branch + /// + /// The current local branch. + /// The new name the existing branch should bear. + /// A new . + public virtual Branch Rename(Branch branch, string newName) + { + return Rename(branch, newName, false); } /// - /// Rename an existing local branch, using the default reflog message + /// Rename an existing local branch /// /// The current local branch. /// The new name the existing branch should bear. /// True to allow silent overwriting a potentially existing branch, false otherwise. /// A new . - public virtual Branch Rename(Branch branch, string newName, bool allowOverwrite = false) + public virtual Branch Rename(Branch branch, string newName, bool allowOverwrite) { - return Rename(branch, newName, null, null, allowOverwrite); + Ensure.ArgumentNotNull(branch, "branch"); + Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); + + if (branch.IsRemote) + { + throw new LibGit2SharpException("Cannot rename branch '{0}'. It's a remote tracking branch.", + branch.FriendlyName); + } + + using (ReferenceHandle referencePtr = repo.Refs.RetrieveReferencePtr(Reference.LocalBranchPrefix + branch.FriendlyName)) + { + using (Proxy.git_branch_move(referencePtr, newName, allowOverwrite)) + { } + } + + var newBranch = this[newName]; + return newBranch; } /// @@ -222,7 +292,7 @@ public virtual Branch Update(Branch branch, params Action[] actio action(updater); } - return this[branch.Name]; + return this[branch.FriendlyName]; } private static bool LooksLikeABranchName(string referenceName) @@ -234,11 +304,7 @@ private static bool LooksLikeABranchName(string referenceName) private string DebuggerDisplay { - get - { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); - } + get { return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } } diff --git a/LibGit2Sharp/BranchCollectionExtensions.cs b/LibGit2Sharp/BranchCollectionExtensions.cs deleted file mode 100644 index 7008eb6af..000000000 --- a/LibGit2Sharp/BranchCollectionExtensions.cs +++ /dev/null @@ -1,101 +0,0 @@ -using LibGit2Sharp.Core; -using System; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class BranchCollectionExtensions - { - /// - /// Create a new local branch with the specified name, using the default reflog message - /// - /// The name of the branch. - /// Revparse spec for the target commit. - /// True to allow silent overwriting a potentially existing branch, false otherwise. - /// The being worked with. - /// A new . - public static Branch Add(this BranchCollection branches, string name, string committish, bool allowOverwrite = false) - { - return Add(branches, name, committish, null, null, allowOverwrite); - } - - /// - /// Create a new local branch with the specified name - /// - /// The being worked with. - /// The name of the branch. - /// Revparse spec for the target commit. - /// The identity used for updating the reflog - /// The optional message to log in the - /// True to allow silent overwriting a potentially existing branch, false otherwise. - /// A new . - public static Branch Add(this BranchCollection branches, string name, string committish, Signature signature, - string logMessage = null, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); - - var commit = branches.repo.LookupCommit(committish); - - if (logMessage == null) - { - var createdFrom = committish != "HEAD" - ? committish - : branches.repo.Info.IsHeadDetached - ? commit.Sha - : branches.repo.Head.Name; - - logMessage = "branch: Created from " + createdFrom; - } - - return branches.Add(name, commit, signature, logMessage, allowOverwrite); - } - - /// - /// Deletes the branch with the specified name. - /// - /// The name of the branch to delete. - /// True if the provided is the name of a remote branch, false otherwise. - /// The being worked with. - public static void Remove(this BranchCollection branches, string name, bool isRemote = false) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - - string branchName = isRemote ? Reference.RemoteTrackingBranchPrefix + name : name; - - Branch branch = branches[branchName]; - - if (branch == null) - { - return; - } - - branches.Remove(branch); - } - - /// - /// Rename an existing local branch, using the default reflog message - /// - /// The current branch name. - /// The new name the existing branch should bear. - /// True to allow silent overwriting a potentially existing branch, false otherwise. - /// The being worked with. - /// A new . - public static Branch Rename(this BranchCollection branches, string currentName, string newName, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); - Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); - - Branch branch = branches[currentName]; - - if (branch == null) - { - throw new LibGit2SharpException("No branch named '{0}' exists in the repository."); - } - - return branches.Rename(branch, newName, allowOverwrite); - } - } -} diff --git a/LibGit2Sharp/BranchTrackingDetails.cs b/LibGit2Sharp/BranchTrackingDetails.cs index ad7153c88..e3d99bd45 100644 --- a/LibGit2Sharp/BranchTrackingDetails.cs +++ b/LibGit2Sharp/BranchTrackingDetails.cs @@ -1,6 +1,4 @@ -using System; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Tracking information for a diff --git a/LibGit2Sharp/BranchUpdater.cs b/LibGit2Sharp/BranchUpdater.cs index f51332eb7..b0908f272 100644 --- a/LibGit2Sharp/BranchUpdater.cs +++ b/LibGit2Sharp/BranchUpdater.cs @@ -125,7 +125,7 @@ private void SetUpstream(string upstreamBranchName) /// The merge branch in the upstream remote's namespace. private void SetUpstreamBranch(string mergeBranchName) { - string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.merge", branch.Name); + string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.merge", branch.FriendlyName); if (string.IsNullOrEmpty(mergeBranchName)) { @@ -143,7 +143,7 @@ private void SetUpstreamBranch(string mergeBranchName) /// The name of the remote to set as the upstream branch. private void SetUpstreamRemote(string remoteName) { - string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.remote", branch.Name); + string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.remote", branch.FriendlyName); if (string.IsNullOrEmpty(remoteName)) { @@ -154,7 +154,7 @@ private void SetUpstreamRemote(string remoteName) if (!remoteName.Equals(".", StringComparison.Ordinal)) { // Verify that remote exists. - repo.Network.Remotes.RemoteForName(remoteName); + using (repo.Network.Remotes.RemoteForName(remoteName)) { } } repo.Config.Set(configKey, remoteName); @@ -183,13 +183,16 @@ private void GetUpstreamInformation(string canonicalName, out string remoteName, { remoteName = Proxy.git_branch_remote_name(repo.Handle, canonicalName, true); - Remote remote = repo.Network.Remotes.RemoteForName(remoteName); - mergeBranchName = remote.FetchSpecTransformToSource(canonicalName); + using (var remote = repo.Network.Remotes.RemoteForName(remoteName)) + { + mergeBranchName = remote.FetchSpecTransformToSource(canonicalName); + } } else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "'{0}' does not look like a valid canonical branch name.", canonicalName)); + "'{0}' does not look like a valid canonical branch name.", + canonicalName)); } } } diff --git a/LibGit2Sharp/BuiltInFeatures.cs b/LibGit2Sharp/BuiltInFeatures.cs index db6a1a0ed..1cf0d92e9 100644 --- a/LibGit2Sharp/BuiltInFeatures.cs +++ b/LibGit2Sharp/BuiltInFeatures.cs @@ -29,5 +29,11 @@ public enum BuiltInFeatures /// libgit2. /// Ssh = (1 << 2), + + /// + /// Support for sub-second resolution in file modification times + /// is compiled into libgit2. + /// + NSec = (1 << 3), } } diff --git a/LibGit2Sharp/Certificate.cs b/LibGit2Sharp/Certificate.cs new file mode 100644 index 000000000..95472a24c --- /dev/null +++ b/LibGit2Sharp/Certificate.cs @@ -0,0 +1,9 @@ +namespace LibGit2Sharp +{ + /// + /// Top-level certificate type. The usable certificates inherit from this class. + /// + public abstract class Certificate + { + } +} diff --git a/LibGit2Sharp/CertificateSsh.cs b/LibGit2Sharp/CertificateSsh.cs new file mode 100644 index 000000000..683c04402 --- /dev/null +++ b/LibGit2Sharp/CertificateSsh.cs @@ -0,0 +1,97 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// This class represents the hostkey which is avaiable when connecting to a SSH host. + /// + public class CertificateSsh : Certificate + { + /// + /// For mocking purposes + /// + protected CertificateSsh() + { } + + /// + /// The MD5 hash of the host. Meaningful if is true + /// + public readonly byte[] HashMD5; + + /// + /// The SHA1 hash of the host. Meaningful if is true + /// + public readonly byte[] HashSHA1; + + /// + /// True if we have the MD5 hostkey hash from the server + /// + public readonly bool HasMD5; + + /// + /// True if we have the SHA1 hostkey hash from the server + /// + public readonly bool HasSHA1; + + internal unsafe CertificateSsh(git_certificate_ssh* cert) + { + + HasMD5 = cert->type.HasFlag(GitCertificateSshType.MD5); + HasSHA1 = cert->type.HasFlag(GitCertificateSshType.SHA1); + + HashMD5 = new byte[16]; + for (var i = 0; i < HashMD5.Length; i++) + { + HashMD5[i] = cert->HashMD5[i]; + } + + HashSHA1 = new byte[20]; + for (var i = 0; i < HashSHA1.Length; i++) + { + HashSHA1[i] = cert->HashSHA1[i]; + } + } + + internal unsafe IntPtr ToPointer() + { + GitCertificateSshType sshCertType = 0; + if (HasMD5) + { + sshCertType |= GitCertificateSshType.MD5; + } + if (HasSHA1) + { + sshCertType |= GitCertificateSshType.SHA1; + } + + var gitCert = new git_certificate_ssh() + { + cert_type = GitCertificateType.Hostkey, + type = sshCertType, + }; + + fixed (byte* p = &HashMD5[0]) + { + for (var i = 0; i < HashMD5.Length; i++) + { + gitCert.HashMD5[i] = p[i]; + } + } + + fixed (byte* p = &HashSHA1[0]) + { + for (var i = 0; i < HashSHA1.Length; i++) + { + gitCert.HashSHA1[i] = p[i]; + } + } + + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert)); + Marshal.StructureToPtr(gitCert, ptr, false); + + return ptr; + } + } +} diff --git a/LibGit2Sharp/CertificateX509.cs b/LibGit2Sharp/CertificateX509.cs new file mode 100644 index 000000000..7b5b0fac6 --- /dev/null +++ b/LibGit2Sharp/CertificateX509.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; +using System.Security.Cryptography.X509Certificates; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Conains a X509 certificate + /// + public class CertificateX509 : Certificate + { + /// + /// For mocking purposes + /// + protected CertificateX509() + { } + + /// + /// The certificate. + /// + public virtual X509Certificate Certificate { get; private set; } + + internal unsafe CertificateX509(git_certificate_x509* cert) + { + int len = checked((int)cert->len.ToUInt32()); + byte[] data = new byte[len]; + Marshal.Copy(new IntPtr(cert->data), data, 0, len); + Certificate = new X509Certificate(data); + } + + internal CertificateX509(X509Certificate cert) + { + Certificate = cert; + } + + internal unsafe IntPtr ToPointers(out IntPtr dataPtr) + { + var certData = Certificate.Export(X509ContentType.Cert); + dataPtr = Marshal.AllocHGlobal(certData.Length); + Marshal.Copy(certData, 0, dataPtr, certData.Length); + var gitCert = new git_certificate_x509() + { + cert_type = GitCertificateType.X509, + data = (byte*)dataPtr.ToPointer(), + len = (UIntPtr)certData.Length, + }; + + var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(gitCert)); + Marshal.StructureToPtr(gitCert, ptr, false); + + return ptr; + } + } +} diff --git a/LibGit2Sharp/ChangeKind.cs b/LibGit2Sharp/ChangeKind.cs index c95095a37..304438be8 100644 --- a/LibGit2Sharp/ChangeKind.cs +++ b/LibGit2Sharp/ChangeKind.cs @@ -55,5 +55,10 @@ public enum ChangeKind /// Entry is unreadable. /// Unreadable = 9, + + /// + /// Entry is currently in conflict. + /// + Conflicted = 10, } } diff --git a/LibGit2Sharp/CheckoutConflictException.cs b/LibGit2Sharp/CheckoutConflictException.cs new file mode 100644 index 000000000..67dc8d2cc --- /dev/null +++ b/LibGit2Sharp/CheckoutConflictException.cs @@ -0,0 +1,74 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when a checkout cannot be performed + /// because of a conflicting change staged in the index, or unstaged + /// in the working directory. + /// +#if NETFRAMEWORK + [Serializable] +#endif + public class CheckoutConflictException : NativeException + { + /// + /// Initializes a new instance of the class. + /// + public CheckoutConflictException() + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A message that describes the error. + public CheckoutConflictException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public CheckoutConflictException(string format, params object[] args) + : base(format, args) + { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. + public CheckoutConflictException(string message, Exception innerException) + : base(message, innerException) + { } + +#if NETFRAMEWORK + /// + /// Initializes a new instance of the class with a serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected CheckoutConflictException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal CheckoutConflictException(string message, GitErrorCategory category) + : base(message, category) + { } + + internal override GitErrorCode ErrorCode + { + get + { + return GitErrorCode.Conflict; + } + } + } +} diff --git a/LibGit2Sharp/CheckoutFileConflictStrategy.cs b/LibGit2Sharp/CheckoutFileConflictStrategy.cs index 578ebe03e..9d53745e7 100644 --- a/LibGit2Sharp/CheckoutFileConflictStrategy.cs +++ b/LibGit2Sharp/CheckoutFileConflictStrategy.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Enum specifying what content checkout should write to disk diff --git a/LibGit2Sharp/CheckoutNotificationOptions.cs b/LibGit2Sharp/CheckoutNotificationOptions.cs index 261727154..649695797 100644 --- a/LibGit2Sharp/CheckoutNotificationOptions.cs +++ b/LibGit2Sharp/CheckoutNotificationOptions.cs @@ -1,5 +1,4 @@ using System; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { diff --git a/LibGit2Sharp/CheckoutOptions.cs b/LibGit2Sharp/CheckoutOptions.cs index 9e297cd47..010502007 100644 --- a/LibGit2Sharp/CheckoutOptions.cs +++ b/LibGit2Sharp/CheckoutOptions.cs @@ -34,8 +34,9 @@ CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy { get { - return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) ? - CheckoutStrategy.GIT_CHECKOUT_FORCE : CheckoutStrategy.GIT_CHECKOUT_SAFE; + return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) + ? CheckoutStrategy.GIT_CHECKOUT_FORCE + : CheckoutStrategy.GIT_CHECKOUT_SAFE; } } diff --git a/LibGit2Sharp/CherryPickOptions.cs b/LibGit2Sharp/CherryPickOptions.cs index 05619f37b..065e79bbb 100644 --- a/LibGit2Sharp/CherryPickOptions.cs +++ b/LibGit2Sharp/CherryPickOptions.cs @@ -1,113 +1,27 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options controlling CherryPick behavior. /// - public sealed class CherryPickOptions : IConvertableToGitCheckoutOpts + public sealed class CherryPickOptions : MergeAndCheckoutOptionsBase { /// /// Initializes a new instance of the class. /// By default the cherry pick will be committed if there are no conflicts. /// public CherryPickOptions() - { - CommitOnSuccess = true; - - FindRenames = true; - - // TODO: libgit2 should provide reasonable defaults for these - // values, but it currently does not. - RenameThreshold = 50; - TargetLimit = 200; - } - - /// - /// The Flags specifying what conditions are - /// reported through the OnCheckoutNotify delegate. - /// - public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } - - /// - /// Delegate that checkout progress will be reported through. - /// - public CheckoutProgressHandler OnCheckoutProgress { get; set; } - - /// - /// Delegate that checkout will notify callers of - /// certain conditions. The conditions that are reported is - /// controlled with the CheckoutNotifyFlags property. - /// - public CheckoutNotifyHandler OnCheckoutNotify { get; set; } - - /// - /// Commit the cherry pick if the cherry pick is successful. - /// - public bool CommitOnSuccess { get; set; } + { } /// /// When cherry picking a merge commit, the parent number to consider as /// mainline, starting from offset 1. /// /// As a merge commit has multiple parents, cherry picking a merge commit - /// will reverse all the changes brought in by the merge except for - /// one parent's line of commits. The parent to preserve is called the - /// mainline, and must be specified by its number (i.e. offset). + /// will take only the changes relative to the given parent. The parent + /// to consider changes based on is called the mainline, and must be + /// specified by its number (i.e. offset). /// /// public int Mainline { get; set; } - - /// - /// How to handle conflicts encountered during a merge. - /// - public MergeFileFavor MergeFileFavor { get; set; } - - /// - /// How Checkout should handle writing out conflicting index entries. - /// - public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } - - /// - /// Find renames. Default is true. - /// - public bool FindRenames { get; set; } - - /// - /// Similarity to consider a file renamed (default 50). If - /// `FindRenames` is enabled, added files will be compared - /// with deleted files to determine their similarity. Files that are - /// more similar than the rename threshold (percentage-wise) will be - /// treated as a rename. - /// - public int RenameThreshold; - - /// - /// Maximum similarity sources to examine for renames (default 200). - /// If the number of rename candidates (add / delete pairs) is greater - /// than this value, inexact rename detection is aborted. - /// - /// This setting overrides the `merge.renameLimit` configuration value. - /// - public int TargetLimit; - - #region IConvertableToGitCheckoutOpts - - CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() - { - return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); - } - - CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy - { - get - { - return CheckoutStrategy.GIT_CHECKOUT_SAFE | - GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); - } - } - - #endregion IConvertableToGitCheckoutOpts } } diff --git a/LibGit2Sharp/CherryPickResult.cs b/LibGit2Sharp/CherryPickResult.cs index b93de8df5..7e944baf7 100644 --- a/LibGit2Sharp/CherryPickResult.cs +++ b/LibGit2Sharp/CherryPickResult.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Class to report the result of a cherry picked. diff --git a/LibGit2Sharp/CloneOptions.cs b/LibGit2Sharp/CloneOptions.cs index 49349daf7..12d47c9f3 100644 --- a/LibGit2Sharp/CloneOptions.cs +++ b/LibGit2Sharp/CloneOptions.cs @@ -1,16 +1,26 @@ -using System; -using LibGit2Sharp.Core; +using LibGit2Sharp.Core; using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// Options to define clone behaviour + /// Options to define clone behavior /// - public sealed class CloneOptions : IConvertableToGitCheckoutOpts, ICredentialsProvider + public sealed class CloneOptions : IConvertableToGitCheckoutOpts { /// - /// Creates default for a non-bare clone + /// Creates with specified for a non-bare clone. + /// + /// The fetch options to use. + public CloneOptions(FetchOptions fetchOptions) : this() + { + Ensure.ArgumentNotNull(fetchOptions, "fetchOptions"); + + FetchOptions = fetchOptions; + } + + /// + /// Creates default for a non-bare clone. /// public CloneOptions() { @@ -29,19 +39,25 @@ public CloneOptions() public bool Checkout { get; set; } /// - /// Handler for network transfer and indexing progress information + /// The name of the branch to checkout. When unspecified the + /// remote's default branch will be used instead. + /// + public string BranchName { get; set; } + + /// + /// Recursively clone submodules. /// - public TransferProgressHandler OnTransferProgress { get; set; } + public bool RecurseSubmodules { get; set; } /// - /// Handler for checkout progress information + /// Handler for checkout progress information. /// public CheckoutProgressHandler OnCheckoutProgress { get; set; } /// - /// Handler to generate for authentication. + /// Gets or sets the fetch options. /// - public CredentialsHandler CredentialsProvider { get; set; } + public FetchOptions FetchOptions { get; } = new(); #region IConvertableToGitCheckoutOpts @@ -54,9 +70,9 @@ CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy { get { - return this.Checkout ? - CheckoutStrategy.GIT_CHECKOUT_SAFE_CREATE : - CheckoutStrategy.GIT_CHECKOUT_NONE; + return this.Checkout + ? CheckoutStrategy.GIT_CHECKOUT_SAFE + : CheckoutStrategy.GIT_CHECKOUT_NONE; } } diff --git a/LibGit2Sharp/Commands/Checkout.cs b/LibGit2Sharp/Commands/Checkout.cs new file mode 100644 index 000000000..46d456be1 --- /dev/null +++ b/LibGit2Sharp/Commands/Checkout.cs @@ -0,0 +1,189 @@ +using System; +using System.Linq; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public static partial class Commands + { + /// + /// Checkout the specified , reference or SHA. + /// + /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will + /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. + /// + /// + /// The repository to act on + /// A revparse spec for the commit or branch to checkout. + /// The that was checked out. + public static Branch Checkout(IRepository repository, string committishOrBranchSpec) + { + return Checkout(repository, committishOrBranchSpec, new CheckoutOptions()); + } + + /// + /// Checkout the specified , reference or SHA. + /// + /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will + /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. + /// + /// + /// The repository to act on + /// A revparse spec for the commit or branch to checkout. + /// controlling checkout behavior. + /// The that was checked out. + public static Branch Checkout(IRepository repository, string committishOrBranchSpec, CheckoutOptions options) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); + Ensure.ArgumentNotNull(options, "options"); + + Reference reference = null; + GitObject obj = null; + Branch branch = null; + + try + { + repository.RevParse(committishOrBranchSpec, out reference, out obj); + } + catch (NotFoundException) + { + // If committishOrBranchSpec is not a local branch but matches a tracking branch + // in exactly one remote, use it. This is the "git checkout" command's default behavior. + // https://git-scm.com/docs/git-checkout#Documentation/git-checkout.txt-emgitcheckoutemltbranchgt + var remoteBranches = repository.Network.Remotes + .SelectMany(r => repository.Branches.Where(b => + b.IsRemote && + b.CanonicalName == $"refs/remotes/{r.Name}/{committishOrBranchSpec}")) + .ToList(); + + if (remoteBranches.Count == 1) + { + branch = repository.CreateBranch(committishOrBranchSpec, remoteBranches[0].Tip); + repository.Branches.Update(branch, b => b.TrackedBranch = remoteBranches[0].CanonicalName); + + return Checkout(repository, branch, options); + } + + if (remoteBranches.Count > 1) + { + throw new AmbiguousSpecificationException($"'{committishOrBranchSpec}' matched multiple ({remoteBranches.Count}) remote tracking branches"); + } + + throw; + } + + if (reference != null && reference.IsLocalBranch) + { + branch = repository.Branches[reference.CanonicalName]; + return Checkout(repository, branch, options); + } + + Commit commit = obj.Peel(true); + Checkout(repository, commit.Tree, options, committishOrBranchSpec); + + return repository.Head; + } + + /// + /// Checkout the tip commit of the specified object. If this commit is the + /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit + /// as a detached HEAD. + /// + /// The repository to act on + /// The to check out. + /// The that was checked out. + public static Branch Checkout(IRepository repository, Branch branch) + { + return Checkout(repository, branch, new CheckoutOptions()); + } + + /// + /// Checkout the tip commit of the specified object. If this commit is the + /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit + /// as a detached HEAD. + /// + /// The repository to act on + /// The to check out. + /// controlling checkout behavior. + /// The that was checked out. + public static Branch Checkout(IRepository repository, Branch branch, CheckoutOptions options) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(branch, "branch"); + Ensure.ArgumentNotNull(options, "options"); + + // Make sure this is not an unborn branch. + if (branch.Tip == null) + { + throw new UnbornBranchException("The tip of branch '{0}' is null. There's nothing to checkout.", + branch.FriendlyName); + } + + if (!branch.IsRemote && !(branch is DetachedHead) && + string.Equals(repository.Refs[branch.CanonicalName].TargetIdentifier, branch.Tip.Id.Sha, + StringComparison.OrdinalIgnoreCase)) + { + Checkout(repository, branch.Tip.Tree, options, branch.CanonicalName); + } + else + { + Checkout(repository, branch.Tip.Tree, options, branch.Tip.Id.Sha); + } + + return repository.Head; + } + + /// + /// Checkout the specified . + /// + /// Will detach the HEAD and make it point to this commit sha. + /// + /// + /// The repository to act on + /// The to check out. + /// The that was checked out. + public static Branch Checkout(IRepository repository, Commit commit) + { + return Checkout(repository, commit, new CheckoutOptions()); + } + + /// + /// Checkout the specified . + /// + /// Will detach the HEAD and make it point to this commit sha. + /// + /// + /// The repository to act on + /// The to check out. + /// controlling checkout behavior. + /// The that was checked out. + public static Branch Checkout(IRepository repository, Commit commit, CheckoutOptions options) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(options, "options"); + + Checkout(repository, commit.Tree, options, commit.Id.Sha); + + return repository.Head; + } + + /// + /// Internal implementation of Checkout that expects the ID of the checkout target + /// to already be in the form of a canonical branch name or a commit ID. + /// + /// The repository to act on + /// The to checkout. + /// controlling checkout behavior. + /// The spec which will be written as target in the reflog. + public static void Checkout(IRepository repository, Tree tree, CheckoutOptions checkoutOptions, string refLogHeadSpec) + { + repository.Checkout(tree, null, checkoutOptions); + + repository.Refs.MoveHeadTarget(refLogHeadSpec); + } + + } +} + diff --git a/LibGit2Sharp/Commands/Fetch.cs b/LibGit2Sharp/Commands/Fetch.cs new file mode 100644 index 000000000..e531aac51 --- /dev/null +++ b/LibGit2Sharp/Commands/Fetch.cs @@ -0,0 +1,85 @@ +using System.Collections.Generic; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Class to serve as namespacing for the command-emulating methods + /// + public static partial class Commands + { + private static RemoteHandle RemoteFromNameOrUrl(RepositoryHandle repoHandle, string remote) + { + RemoteHandle handle = null; + handle = Proxy.git_remote_lookup(repoHandle, remote, false); + + // If that wasn't the name of a remote, let's use it as a url + if (handle == null) + { + handle = Proxy.git_remote_create_anonymous(repoHandle, remote); + } + + return handle; + } + + /// + /// Perform a fetch + /// + /// The repository in which to fetch. + /// The remote to fetch from. Either as a remote name or a URL + /// Fetch options. + /// Log message for any ref updates. + /// List of refspecs to apply as active. + public static void Fetch(Repository repository, string remote, IEnumerable refspecs, FetchOptions options, string logMessage) + { + Ensure.ArgumentNotNull(remote, "remote"); + + options = options ?? new FetchOptions(); + using (var remoteHandle = RemoteFromNameOrUrl(repository.Handle, remote)) + using (var fetchOptionsWrapper = new GitFetchOptionsWrapper()) + { + + var callbacks = new RemoteCallbacks(options); + GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); + + // It is OK to pass the reference to the GitCallbacks directly here because libgit2 makes a copy of + // the data in the git_remote_callbacks structure. If, in the future, libgit2 changes its implementation + // to store a reference to the git_remote_callbacks structure this would introduce a subtle bug + // where the managed layer could move the git_remote_callbacks to a different location in memory, + // but libgit2 would still reference the old address. + // + // Also, if GitRemoteCallbacks were a class instead of a struct, we would need to guard against + // GC occuring in between setting the remote callbacks and actual usage in one of the functions afterwords. + var fetchOptions = fetchOptionsWrapper.Options; + fetchOptions.RemoteCallbacks = gitCallbacks; + fetchOptions.download_tags = Proxy.git_remote_autotag(remoteHandle); + + if (options.TagFetchMode.HasValue) + { + fetchOptions.download_tags = options.TagFetchMode.Value; + } + + if (options.Prune.HasValue) + { + fetchOptions.Prune = options.Prune.Value ? FetchPruneStrategy.Prune : FetchPruneStrategy.NoPrune; + } + else + { + fetchOptions.Prune = FetchPruneStrategy.FromConfigurationOrDefault; + } + + if (options.CustomHeaders != null && options.CustomHeaders.Length > 0) + { + fetchOptions.CustomHeaders = GitStrArrayManaged.BuildFrom(options.CustomHeaders); + } + + fetchOptions.ProxyOptions = options.ProxyOptions.CreateGitProxyOptions(); + + Proxy.git_remote_fetch(remoteHandle, refspecs, fetchOptions, logMessage); + } + + } + } +} + diff --git a/LibGit2Sharp/Commands/Pull.cs b/LibGit2Sharp/Commands/Pull.cs new file mode 100644 index 000000000..f0a68fe9b --- /dev/null +++ b/LibGit2Sharp/Commands/Pull.cs @@ -0,0 +1,41 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Fetch changes from the configured upstream remote and branch into the branch pointed at by HEAD. + /// + public static partial class Commands + { + /// + /// Fetch changes from the configured upstream remote and branch into the branch pointed at by HEAD. + /// + /// The repository. + /// The signature to use for the merge. + /// The options for fetch and merging. + public static MergeResult Pull(Repository repository, Signature merger, PullOptions options) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(merger, "merger"); + + + options = options ?? new PullOptions(); + Branch currentBranch = repository.Head; + + if (!currentBranch.IsTracking) + { + throw new LibGit2SharpException("There is no tracking information for the current branch."); + } + + if (currentBranch.RemoteName == null) + { + throw new LibGit2SharpException("No upstream remote for the current branch."); + } + + Commands.Fetch(repository, currentBranch.RemoteName, Array.Empty(), options.FetchOptions, null); + return repository.MergeFetchedRefs(merger, options.MergeOptions); + } + } +} + diff --git a/LibGit2Sharp/Commands/Remove.cs b/LibGit2Sharp/Commands/Remove.cs new file mode 100644 index 000000000..f96339c12 --- /dev/null +++ b/LibGit2Sharp/Commands/Remove.cs @@ -0,0 +1,241 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public static partial class Commands + { + + /// + /// Removes a file from the staging area, and optionally removes it from the working directory as well. + /// + /// If the file has already been deleted from the working directory, this method will only deal + /// with promoting the removal to the staging area. + /// + /// + /// The default behavior is to remove the file from the working directory as well. + /// + /// + /// The being worked with. + /// The path of the file within the working directory. + public static void Remove(IRepository repository, string path) + { + Remove(repository, path, true, null); + } + + /// + /// Removes a file from the staging area, and optionally removes it from the working directory as well. + /// + /// If the file has already been deleted from the working directory, this method will only deal + /// with promoting the removal to the staging area. + /// + /// + /// The default behavior is to remove the file from the working directory as well. + /// + /// + /// The being worked with. + /// The path of the file within the working directory. + /// True to remove the file from the working directory, False otherwise. + public static void Remove(IRepository repository, string path, bool removeFromWorkingDirectory) + { + Remove(repository, path, removeFromWorkingDirectory, null); + } + + + /// + /// Removes a file from the staging area, and optionally removes it from the working directory as well. + /// + /// If the file has already been deleted from the working directory, this method will only deal + /// with promoting the removal to the staging area. + /// + /// + /// The default behavior is to remove the file from the working directory as well. + /// + /// + /// When not passing a , the passed path will be treated as + /// a pathspec. You can for example use it to pass the relative path to a folder inside the working directory, + /// so that all files beneath this folders, and the folder itself, will be removed. + /// + /// + /// The repository in which to operate + /// The path of the file within the working directory. + /// True to remove the file from the working directory, False otherwise. + /// + /// The passed will be treated as an explicit path. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + public static void Remove(IRepository repository, string path, bool removeFromWorkingDirectory, ExplicitPathsOptions explicitPathsOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(path, "path"); + + Remove(repository, new[] { path }, removeFromWorkingDirectory, explicitPathsOptions); + } + + /// + /// Removes a collection of fileS from the staging, and optionally removes them from the working directory as well. + /// + /// If a file has already been deleted from the working directory, this method will only deal + /// with promoting the removal to the staging area. + /// + /// + /// The default behavior is to remove the files from the working directory as well. + /// + /// + /// The being worked with. + /// The collection of paths of the files within the working directory. + public static void Remove(IRepository repository, IEnumerable paths) + { + Remove(repository, paths, true, null); + } + + /// + /// Removes a collection of fileS from the staging, and optionally removes them from the working directory as well. + /// + /// If a file has already been deleted from the working directory, this method will only deal + /// with promoting the removal to the staging area. + /// + /// + /// The default behavior is to remove the files from the working directory as well. + /// + /// + /// When not passing a , the passed paths will be treated as + /// a pathspec. You can for example use it to pass the relative paths to folders inside the working directory, + /// so that all files beneath these folders, and the folders themselves, will be removed. + /// + /// + /// The repository in which to operate + /// The collection of paths of the files within the working directory. + /// True to remove the files from the working directory, False otherwise. + /// + /// The passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + public static void Remove(IRepository repository, IEnumerable paths, bool removeFromWorkingDirectory, ExplicitPathsOptions explicitPathsOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNullOrEmptyEnumerable(paths, "paths"); + + var pathsToDelete = paths.Where(p => Directory.Exists(Path.Combine(repository.Info.WorkingDirectory, p))).ToList(); + var notConflictedPaths = new List(); + var index = repository.Index; + + foreach (var path in paths) + { + Ensure.ArgumentNotNullOrEmptyString(path, "path"); + + var conflict = index.Conflicts[path]; + + if (conflict != null) + { + index.Remove(path); + pathsToDelete.Add(path); + } + else + { + notConflictedPaths.Add(path); + } + } + + // Make sure status will see the changes from before this + index.Write(); + + if (notConflictedPaths.Count > 0) + { + pathsToDelete.AddRange(RemoveStagedItems(repository, notConflictedPaths, removeFromWorkingDirectory, explicitPathsOptions)); + } + + if (removeFromWorkingDirectory) + { + RemoveFilesAndFolders(repository, pathsToDelete); + } + + index.Write(); + } + + private static void RemoveFilesAndFolders(IRepository repository, IEnumerable pathsList) + { + string wd = repository.Info.WorkingDirectory; + + foreach (string path in pathsList) + { + string fileName = Path.Combine(wd, path); + + if (Directory.Exists(fileName)) + { + Directory.Delete(fileName, true); + continue; + } + + if (!File.Exists(fileName)) + { + continue; + } + + File.Delete(fileName); + } + } + + private static IEnumerable RemoveStagedItems(IRepository repository, IEnumerable paths, bool removeFromWorkingDirectory = true, ExplicitPathsOptions explicitPathsOptions = null) + { + var removed = new List(); + using (var changes = repository.Diff.Compare(DiffModifiers.IncludeUnmodified | DiffModifiers.IncludeUntracked, paths, explicitPathsOptions)) + { + var index = repository.Index; + + foreach (var treeEntryChanges in changes) + { + var status = repository.RetrieveStatus(treeEntryChanges.Path); + + switch (treeEntryChanges.Status) + { + case ChangeKind.Added: + case ChangeKind.Deleted: + removed.Add(treeEntryChanges.Path); + index.Remove(treeEntryChanges.Path); + break; + + case ChangeKind.Unmodified: + if (removeFromWorkingDirectory && ( + status.HasFlag(FileStatus.ModifiedInIndex) || + status.HasFlag(FileStatus.NewInIndex))) + { + throw new RemoveFromIndexException("Unable to remove file '{0}', as it has changes staged in the index. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", + treeEntryChanges.Path); + } + removed.Add(treeEntryChanges.Path); + index.Remove(treeEntryChanges.Path); + continue; + + case ChangeKind.Modified: + if (status.HasFlag(FileStatus.ModifiedInWorkdir) && status.HasFlag(FileStatus.ModifiedInIndex)) + { + throw new RemoveFromIndexException("Unable to remove file '{0}', as it has staged content different from both the working directory and the HEAD.", + treeEntryChanges.Path); + } + if (removeFromWorkingDirectory) + { + throw new RemoveFromIndexException("Unable to remove file '{0}', as it has local modifications. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", + treeEntryChanges.Path); + } + removed.Add(treeEntryChanges.Path); + index.Remove(treeEntryChanges.Path); + continue; + + default: + throw new RemoveFromIndexException("Unable to remove file '{0}'. Its current status is '{1}'.", + treeEntryChanges.Path, + treeEntryChanges.Status); + } + } + + index.Write(); + + return removed; + } + } + } +} + diff --git a/LibGit2Sharp/Commands/Stage.cs b/LibGit2Sharp/Commands/Stage.cs new file mode 100644 index 000000000..d11bf6f76 --- /dev/null +++ b/LibGit2Sharp/Commands/Stage.cs @@ -0,0 +1,324 @@ +using System; +using System.IO; +using System.Linq; +using System.Globalization; +using System.Collections.Generic; +using LibGit2Sharp; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public static partial class Commands + { + /// + /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). + /// + /// If this path is ignored by configuration then it will not be staged unless is unset. + /// + /// The repository in which to act + /// The path of the file within the working directory. + public static void Stage(IRepository repository, string path) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(path, "path"); + + Stage(repository, new[] { path }, null); + } + + /// + /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). + /// + /// If this path is ignored by configuration then it will not be staged unless is unset. + /// + /// The repository in which to act + /// The path of the file within the working directory. + /// Determines how paths will be staged. + public static void Stage(IRepository repository, string path, StageOptions stageOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(path, "path"); + + Stage(repository, new[] { path }, stageOptions); + } + + /// + /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). + /// + /// Any paths (even those listed explicitly) that are ignored by configuration will not be staged unless is unset. + /// + /// The repository in which to act + /// The collection of paths of the files within the working directory. + public static void Stage(IRepository repository, IEnumerable paths) + { + Stage(repository, paths, null); + } + + /// + /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). + /// + /// Any paths (even those listed explicitly) that are ignored by configuration will not be staged unless is unset. + /// + /// The repository in which to act + /// The collection of paths of the files within the working directory. + /// Determines how paths will be staged. + public static void Stage(IRepository repository, IEnumerable paths, StageOptions stageOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(paths, "paths"); + + DiffModifiers diffModifiers = DiffModifiers.IncludeUntracked; + ExplicitPathsOptions explicitPathsOptions = stageOptions != null ? stageOptions.ExplicitPathsOptions : null; + + if (stageOptions != null && stageOptions.IncludeIgnored) + { + diffModifiers |= DiffModifiers.IncludeIgnored; + } + + using (var changes = repository.Diff.Compare(diffModifiers, paths, explicitPathsOptions, + new CompareOptions { Similarity = SimilarityOptions.None })) + { + var unexpectedTypesOfChanges = changes + .Where( + tec => tec.Status != ChangeKind.Added && + tec.Status != ChangeKind.Modified && + tec.Status != ChangeKind.Conflicted && + tec.Status != ChangeKind.Unmodified && + tec.Status != ChangeKind.Deleted).ToList(); + + if (unexpectedTypesOfChanges.Count > 0) + { + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected ChangeKind '{1}'", + unexpectedTypesOfChanges[0].Path, unexpectedTypesOfChanges[0].Status)); + } + + /* Remove files from the index that don't exist on disk */ + foreach (TreeEntryChanges treeEntryChanges in changes) + { + switch (treeEntryChanges.Status) + { + case ChangeKind.Conflicted: + if (!treeEntryChanges.Exists) + { + repository.Index.Remove(treeEntryChanges.Path); + } + break; + + case ChangeKind.Deleted: + repository.Index.Remove(treeEntryChanges.Path); + break; + + default: + continue; + } + } + + foreach (TreeEntryChanges treeEntryChanges in changes) + { + switch (treeEntryChanges.Status) + { + case ChangeKind.Added: + case ChangeKind.Modified: + repository.Index.Add(treeEntryChanges.Path); + break; + + case ChangeKind.Conflicted: + if (treeEntryChanges.Exists) + { + repository.Index.Add(treeEntryChanges.Path); + } + break; + + default: + continue; + } + } + + repository.Index.Write(); + } + } + + /// + /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). + /// + /// The repository in which to act + /// The path of the file within the working directory. + public static void Unstage(IRepository repository, string path) + { + Unstage(repository, path, null); + } + + /// + /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). + /// + /// The repository in which to act + /// The path of the file within the working directory. + /// + /// The passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + public static void Unstage(IRepository repository, string path, ExplicitPathsOptions explicitPathsOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(path, "path"); + + Unstage(repository, new[] { path }, explicitPathsOptions); + } + + /// + /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). + /// + /// The repository in which to act + /// The collection of paths of the files within the working directory. + public static void Unstage(IRepository repository, IEnumerable paths) + { + Unstage(repository, paths, null); + } + + /// + /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). + /// + /// The repository in which to act + /// The collection of paths of the files within the working directory. + /// + /// The passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + public static void Unstage(IRepository repository, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(paths, "paths"); + + if (repository.Info.IsHeadUnborn) + { + using (var changes = repository.Diff.Compare(null, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None })) + repository.Index.Replace(changes); + } + else + { + repository.Index.Replace(repository.Head.Tip, paths, explicitPathsOptions); + } + + repository.Index.Write(); + } + + /// + /// Moves and/or renames a file in the working directory and promotes the change to the staging area. + /// + /// The repository to act on + /// The path of the file within the working directory which has to be moved/renamed. + /// The target path of the file within the working directory. + public static void Move(IRepository repository, string sourcePath, string destinationPath) + { + Move(repository, new[] { sourcePath }, new[] { destinationPath }); + } + + /// + /// Moves and/or renames a collection of files in the working directory and promotes the changes to the staging area. + /// + /// The repository to act on + /// The paths of the files within the working directory which have to be moved/renamed. + /// The target paths of the files within the working directory. + public static void Move(IRepository repository, IEnumerable sourcePaths, IEnumerable destinationPaths) + { + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(sourcePaths, "sourcePaths"); + Ensure.ArgumentNotNull(destinationPaths, "destinationPaths"); + + //TODO: Move() should support following use cases: + // - Moving a file under a directory ('file' and 'dir' -> 'dir/file') + // - Moving a directory (and its content) under another directory ('dir1' and 'dir2' -> 'dir2/dir1/*') + + //TODO: Move() should throw when: + // - Moving a directory under a file + + IDictionary, Tuple> batch = PrepareBatch(repository, sourcePaths, destinationPaths); + + if (batch.Count == 0) + { + throw new ArgumentNullException(nameof(sourcePaths)); + } + + foreach (KeyValuePair, Tuple> keyValuePair in batch) + { + string sourcePath = keyValuePair.Key.Item1; + string destPath = keyValuePair.Value.Item1; + + if (Directory.Exists(sourcePath) || Directory.Exists(destPath)) + { + throw new NotImplementedException(); + } + + FileStatus sourceStatus = keyValuePair.Key.Item2; + if (sourceStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromIndex, FileStatus.NewInWorkdir, FileStatus.DeletedFromWorkdir })) + { + throw new LibGit2SharpException("Unable to move file '{0}'. Its current status is '{1}'.", + sourcePath, + sourceStatus); + } + + FileStatus desStatus = keyValuePair.Value.Item2; + if (desStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.DeletedFromWorkdir })) + { + continue; + } + + throw new LibGit2SharpException("Unable to overwrite file '{0}'. Its current status is '{1}'.", + destPath, + desStatus); + } + + string wd = repository.Info.WorkingDirectory; + var index = repository.Index; + foreach (KeyValuePair, Tuple> keyValuePair in batch) + { + string from = keyValuePair.Key.Item1; + string to = keyValuePair.Value.Item1; + + index.Remove(from); + File.Move(Path.Combine(wd, from), Path.Combine(wd, to)); + index.Add(to); + } + + index.Write(); + } + + private static bool Enumerate(IEnumerator leftEnum, IEnumerator rightEnum) + { + bool isLeftEoF = leftEnum.MoveNext(); + bool isRightEoF = rightEnum.MoveNext(); + + if (isLeftEoF == isRightEoF) + { + return isLeftEoF; + } + + throw new ArgumentException("The collection of paths are of different lengths."); + } + + private static IDictionary, Tuple> PrepareBatch(IRepository repository, IEnumerable leftPaths, IEnumerable rightPaths) + { + IDictionary, Tuple> dic = new Dictionary, Tuple>(); + + IEnumerator leftEnum = leftPaths.GetEnumerator(); + IEnumerator rightEnum = rightPaths.GetEnumerator(); + + while (Enumerate(leftEnum, rightEnum)) + { + Tuple from = BuildFrom(repository, leftEnum.Current); + Tuple to = BuildFrom(repository, rightEnum.Current); + dic.Add(from, to); + } + + return dic; + } + + private static Tuple BuildFrom(IRepository repository, string path) + { + string relativePath = repository.BuildRelativePathFrom(path); + return new Tuple(relativePath, repository.RetrieveStatus(relativePath)); + } + } +} + diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index 6ea9d0608..357567d8a 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -54,7 +54,7 @@ internal Commit(Repository repo, ObjectId id) /// /// Gets the pointed at by the in the . /// - /// The relative path to the from the working directory. + /// Path to the from the tree in this /// null if nothing has been found, the otherwise. public virtual TreeEntry this[string relativePath] { @@ -106,22 +106,100 @@ private IEnumerable RetrieveNotesOfCommit(ObjectId oid) return repo.Notes[oid]; } - private static string RetrieveEncodingOf(GitObjectSafeHandle obj) + private static string RetrieveEncodingOf(ObjectHandle obj) { string encoding = Proxy.git_commit_message_encoding(obj); return encoding ?? "UTF-8"; } + /// + /// Prettify a commit message + /// + /// Remove comment lines and trailing lines + /// + /// + /// The prettified message + /// The message to prettify. + /// Comment character. Lines starting with it will be removed + public static string PrettifyMessage(string message, char commentChar) + { + return Proxy.git_message_prettify(message, commentChar); + } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "{0} {1}", Id.ToString(7), MessageShort); + "{0} {1}", + Id.ToString(7), + MessageShort); } } + /// + /// Extract the signature data from this commit + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + /// The header field which contains the signature; use null for the default of "gpgsig" + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id, string field) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, field); + } + + /// + /// Extract the signature data from this commit + /// + /// The overload uses the default header field "gpgsig" + /// + /// + /// The signature and the signed data + /// The repository in which the object lives + /// The commit to extract the signature from + public static SignatureInfo ExtractSignature(Repository repo, ObjectId id) + { + return Proxy.git_commit_extract_signature(repo.Handle, id, null); + } + + /// + /// Create a commit in-memory + /// + /// Prettifing the message includes: + /// * Removing empty lines from the beginning and end. + /// * Removing trailing spaces from every line. + /// * Turning multiple consecutive empty lines between paragraphs into just one empty line. + /// * Ensuring the commit message ends with a newline. + /// * Removing every line starting with the . + /// + /// + /// The of who made the change. + /// The of who added the change to the repository. + /// The description of why a change was made to the repository. + /// The of the to be created. + /// The parents of the to be created. + /// True to prettify the message, or false to leave it as is. + /// When non null, lines starting with this character will be stripped if prettifyMessage is true. + /// The contents of the commit object. + public static string CreateBuffer(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage, char? commentChar) + { + Ensure.ArgumentNotNull(message, "message"); + Ensure.ArgumentDoesNotContainZeroByte(message, "message"); + Ensure.ArgumentNotNull(author, "author"); + Ensure.ArgumentNotNull(committer, "committer"); + Ensure.ArgumentNotNull(tree, "tree"); + Ensure.ArgumentNotNull(parents, "parents"); + + if (prettifyMessage) + { + message = Proxy.git_message_prettify(message, commentChar); + } + + return Proxy.git_commit_create_buffer(tree.repo.Handle, author, committer, message, tree, parents.ToArray()); + } + private class ParentsCollection : ICollection { private readonly Lazy> _parents; diff --git a/LibGit2Sharp/CommitFilter.cs b/LibGit2Sharp/CommitFilter.cs index 5021c1260..8997ca772 100644 --- a/LibGit2Sharp/CommitFilter.cs +++ b/LibGit2Sharp/CommitFilter.cs @@ -1,11 +1,12 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Linq; namespace LibGit2Sharp { /// - /// Criterias used to filter out and order the commits of the repository when querying its history. + /// Criteria used to filter out and order the commits of the repository when querying its history. /// public sealed class CommitFilter { @@ -15,12 +16,12 @@ public sealed class CommitFilter public CommitFilter() { SortBy = CommitSortStrategies.Time; - Since = "HEAD"; + IncludeReachableFrom = "HEAD"; FirstParentOnly = false; } /// - /// The ordering stragtegy to use. + /// The ordering strategy to use. /// /// By default, the commits are shown in reverse chronological order. /// @@ -36,11 +37,11 @@ public CommitFilter() /// By default, the will be used as boundary. /// /// - public object Since { get; set; } + public object IncludeReachableFrom { get; set; } internal IList SinceList { - get { return ToList(Since); } + get { return ToList(IncludeReachableFrom); } } /// @@ -51,11 +52,11 @@ internal IList SinceList /// a , an or even a mixed collection of all of the above. /// /// - public object Until { get; set; } + public object ExcludeReachableFrom { get; set; } internal IList UntilList { - get { return ToList(Until); } + get { return ToList(ExcludeReachableFrom); } } /// @@ -73,12 +74,12 @@ private static IList ToList(object obj) } var types = new[] - { - typeof(string), typeof(ObjectId), - typeof(Commit), typeof(TagAnnotation), - typeof(Tag), typeof(Branch), typeof(DetachedHead), - typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) - }; + { + typeof(string), typeof(ObjectId), + typeof(Commit), typeof(TagAnnotation), + typeof(Tag), typeof(Branch), typeof(DetachedHead), + typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) + }; if (types.Contains(obj.GetType())) { diff --git a/LibGit2Sharp/CommitLog.cs b/LibGit2Sharp/CommitLog.cs index 1056437af..4a6ab1de3 100644 --- a/LibGit2Sharp/CommitLog.cs +++ b/LibGit2Sharp/CommitLog.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -23,8 +22,7 @@ public sealed class CommitLog : IQueryableCommitLog /// The repository. internal CommitLog(Repository repo) : this(repo, new CommitFilter()) - { - } + { } /// /// Initializes a new instance of the class. @@ -75,74 +73,42 @@ IEnumerator IEnumerable.GetEnumerator() public ICommitLog QueryBy(CommitFilter filter) { Ensure.ArgumentNotNull(filter, "filter"); - Ensure.ArgumentNotNull(filter.Since, "filter.Since"); - Ensure.ArgumentNotNullOrEmptyString(filter.Since.ToString(), "filter.Since"); + Ensure.ArgumentNotNull(filter.IncludeReachableFrom, "filter.IncludeReachableFrom"); + Ensure.ArgumentNotNullOrEmptyString(filter.IncludeReachableFrom.ToString(), "filter.IncludeReachableFrom"); return new CommitLog(repo, filter); } /// - /// Find the best possible merge base given two s. + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The first . - /// The second . - /// The merge base or null if none found. - public Commit FindMergeBase(Commit first, Commit second) + /// The file's path. + /// A list of file history entries, ready to be enumerated. + public IEnumerable QueryBy(string path) { - Ensure.ArgumentNotNull(first, "first"); - Ensure.ArgumentNotNull(second, "second"); + Ensure.ArgumentNotNull(path, "path"); - return FindMergeBase(new[] { first, second }, MergeBaseFindingStrategy.Standard); + return new FileHistory(repo, path); } /// - /// Find the best possible merge base given two or more according to the . + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The s for which to find the merge base. - /// The strategy to leverage in order to find the merge base. - /// The merge base or null if none found. - public Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy) + /// The file's path. + /// The options used to control which commits will be returned. + /// A list of file history entries, ready to be enumerated. + public IEnumerable QueryBy(string path, CommitFilter filter) { - Ensure.ArgumentNotNull(commits, "commits"); - - ObjectId id; - List ids = new List(8); - int count = 0; - - foreach (var commit in commits) - { - if (commit == null) - { - throw new ArgumentException("Enumerable contains null at position: " + count.ToString(CultureInfo.InvariantCulture), "commits"); - } - ids.Add(commit.Id.Oid); - count++; - } - - if (count < 2) - { - throw new ArgumentException("The enumerable must contains at least two commits.", "commits"); - } - - switch (strategy) - { - case MergeBaseFindingStrategy.Standard: - id = Proxy.git_merge_base_many(repo.Handle, ids.ToArray()); - break; - case MergeBaseFindingStrategy.Octopus: - id = Proxy.git_merge_base_octopus(repo.Handle, ids.ToArray()); - break; - default: - throw new ArgumentException("", "strategy"); - } + Ensure.ArgumentNotNull(path, "path"); + Ensure.ArgumentNotNull(filter, "filter"); - return id == null ? null : repo.Lookup(id); + return new FileHistory(repo, path, filter); } private class CommitEnumerator : IEnumerator { private readonly Repository repo; - private readonly RevWalkerSafeHandle handle; + private readonly RevWalkerHandle handle; private ObjectId currentOid; public CommitEnumerator(Repository repo, CommitFilter filter) @@ -201,7 +167,7 @@ private void Dispose(bool disposing) handle.SafeDispose(); } - private delegate void HidePushSignature(RevWalkerSafeHandle handle, ObjectId id); + private delegate void HidePushSignature(RevWalkerHandle handle, ObjectId id); private void InternalHidePush(IList identifier, HidePushSignature hidePush) { @@ -241,7 +207,6 @@ private void FirstParentOnly(bool firstParent) } } } - } /// diff --git a/LibGit2Sharp/CommitOptions.cs b/LibGit2Sharp/CommitOptions.cs index c36fb4334..ffd4c23d2 100644 --- a/LibGit2Sharp/CommitOptions.cs +++ b/LibGit2Sharp/CommitOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Provides optional additional information to commit creation. diff --git a/LibGit2Sharp/CommitRewriteInfo.cs b/LibGit2Sharp/CommitRewriteInfo.cs index 5e0a5caaa..ca7399578 100644 --- a/LibGit2Sharp/CommitRewriteInfo.cs +++ b/LibGit2Sharp/CommitRewriteInfo.cs @@ -28,11 +28,51 @@ public sealed class CommitRewriteInfo public static CommitRewriteInfo From(Commit commit) { return new CommitRewriteInfo - { - Author = commit.Author, - Committer = commit.Committer, - Message = commit.Message - }; + { + Author = commit.Author, + Committer = commit.Committer, + Message = commit.Message + }; + } + + /// + /// Build a from the passed in, + /// optionally overriding some of its properties + /// + /// The whose information is to be copied + /// Optional override for the author + /// A new object that matches the info for the + /// with the optional parameters replaced.. + public static CommitRewriteInfo From(Commit commit, Signature author) + { + return From(commit, author, null, null); + } + + /// + /// Build a from the passed in, + /// optionally overriding some of its properties + /// + /// The whose information is to be copied + /// Optional override for the message + /// A new object that matches the info for the + /// with the optional parameters replaced.. + public static CommitRewriteInfo From(Commit commit, string message) + { + return From(commit, null, null, message); + } + + /// + /// Build a from the passed in, + /// optionally overriding some of its properties + /// + /// The whose information is to be copied + /// Optional override for the author + /// Optional override for the committer + /// A new object that matches the info for the + /// with the optional parameters replaced.. + public static CommitRewriteInfo From(Commit commit, Signature author, Signature committer) + { + return From(commit, author, committer, null); } /// @@ -45,10 +85,11 @@ public static CommitRewriteInfo From(Commit commit) /// Optional override for the message /// A new object that matches the info for the /// with the optional parameters replaced.. - public static CommitRewriteInfo From(Commit commit, - Signature author = null, - Signature committer = null, - string message = null) + public static CommitRewriteInfo From( + Commit commit, + Signature author, + Signature committer, + string message) { var cri = From(commit); cri.Author = author ?? cri.Author; diff --git a/LibGit2Sharp/CompareOptions.cs b/LibGit2Sharp/CompareOptions.cs index 09f47a2ec..fb4234439 100644 --- a/LibGit2Sharp/CompareOptions.cs +++ b/LibGit2Sharp/CompareOptions.cs @@ -14,6 +14,7 @@ public CompareOptions() { ContextLines = 3; InterhunkLines = 0; + Algorithm = DiffAlgorithm.Myers; } /// @@ -37,5 +38,17 @@ public CompareOptions() /// Include "unmodified" entries in the results. /// public bool IncludeUnmodified { get; set; } + + /// + /// Algorithm to be used when performing a Diff. + /// By default, will be used. + /// + public DiffAlgorithm Algorithm { get; set; } + + /// + /// Enable --indent-heuristic Diff option, that attempts to produce more aesthetically pleasing diffs. + /// By default, this option will be false. + /// + public bool IndentHeuristic { get; set; } } } diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 9e0a2633d..84a8a3e53 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -14,13 +15,13 @@ namespace LibGit2Sharp public class Configuration : IDisposable, IEnumerable> { + private readonly FilePath repoConfigPath; private readonly FilePath globalConfigPath; private readonly FilePath xdgConfigPath; private readonly FilePath systemConfigPath; + private readonly FilePath programDataConfigPath; - private readonly Repository repository; - - private ConfigurationSafeHandle configHandle; + private ConfigurationHandle configHandle; /// /// Needed for mocking purposes. @@ -28,59 +29,178 @@ public class Configuration : IDisposable, protected Configuration() { } - internal Configuration(Repository repository, string globalConfigurationFileLocation, - string xdgConfigurationFileLocation, string systemConfigurationFileLocation) + internal Configuration( + Repository repository, + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation, + string systemConfigurationFileLocation) { - this.repository = repository; + if (repositoryConfigurationFileLocation != null) + { + repoConfigPath = NormalizeConfigPath(repositoryConfigurationFileLocation); + } globalConfigPath = globalConfigurationFileLocation ?? Proxy.git_config_find_global(); xdgConfigPath = xdgConfigurationFileLocation ?? Proxy.git_config_find_xdg(); systemConfigPath = systemConfigurationFileLocation ?? Proxy.git_config_find_system(); + programDataConfigPath = Proxy.git_config_find_programdata(); - Init(); + Init(repository); } - private void Init() + private void Init(Repository repository) { configHandle = Proxy.git_config_new(); + RepositoryHandle repoHandle = (repository != null) ? repository.Handle : null; - if (repository != null) + if (repoHandle != null) { //TODO: push back this logic into libgit2. // As stated by @carlosmn "having a helper function to load the defaults and then allowing you // to modify it before giving it to git_repository_open_ext() would be a good addition, I think." // -- Agreed :) string repoConfigLocation = Path.Combine(repository.Info.Path, "config"); - Proxy.git_config_add_file_ondisk(configHandle, repoConfigLocation, ConfigurationLevel.Local); + Proxy.git_config_add_file_ondisk(configHandle, repoConfigLocation, ConfigurationLevel.Local, repoHandle); - Proxy.git_repository_set_config(repository.Handle, configHandle); + Proxy.git_repository_set_config(repoHandle, configHandle); + } + else if (repoConfigPath != null) + { + Proxy.git_config_add_file_ondisk(configHandle, repoConfigPath, ConfigurationLevel.Local, repoHandle); } if (globalConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, globalConfigPath, ConfigurationLevel.Global); + Proxy.git_config_add_file_ondisk(configHandle, globalConfigPath, ConfigurationLevel.Global, repoHandle); } if (xdgConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, xdgConfigPath, ConfigurationLevel.Xdg); + Proxy.git_config_add_file_ondisk(configHandle, xdgConfigPath, ConfigurationLevel.Xdg, repoHandle); } if (systemConfigPath != null) { - Proxy.git_config_add_file_ondisk(configHandle, systemConfigPath, ConfigurationLevel.System); + Proxy.git_config_add_file_ondisk(configHandle, systemConfigPath, ConfigurationLevel.System, repoHandle); + } + + if (programDataConfigPath != null) + { + Proxy.git_config_add_file_ondisk(configHandle, programDataConfigPath, ConfigurationLevel.ProgramData, repoHandle); + } + } + + private FilePath NormalizeConfigPath(FilePath path) + { + if (File.Exists(path.Native)) + { + return path; } + + if (!Directory.Exists(path.Native)) + { + throw new FileNotFoundException("Cannot find repository configuration file", path.Native); + } + + var configPath = Path.Combine(path.Native, "config"); + + if (File.Exists(configPath)) + { + return configPath; + } + + var gitConfigPath = Path.Combine(path.Native, ".git", "config"); + + if (File.Exists(gitConfigPath)) + { + return gitConfigPath; + } + + throw new FileNotFoundException("Cannot find repository configuration file", path.Native); + } + + /// + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// + /// + /// Path to an existing Repository configuration file. + /// An instance of . + public static Configuration BuildFrom(string repositoryConfigurationFileLocation) + { + return BuildFrom(repositoryConfigurationFileLocation, null, null, null); + } + + /// + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// + /// + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation) + { + return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, null, null); } /// - /// Access configuration values without a repository. Generally you want to access configuration via an instance of instead. + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// /// - /// Path to a Global configuration file. If null, the default path for a global configuration file will be probed. + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. /// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed. - /// Path to a System configuration file. If null, the default path for a system configuration file will be probed. - public Configuration(string globalConfigurationFileLocation = null, string xdgConfigurationFileLocation = null, string systemConfigurationFileLocation = null) - : this(null, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation) + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation) { + return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null); + } + + /// + /// Access configuration values without a repository. + /// + /// Generally you want to access configuration via an instance of instead. + /// + /// + /// can either contains a path to a file or a directory. In the latter case, + /// this can be the working directory, the .git directory or the directory containing a bare repository. + /// + /// + /// Path to an existing Repository configuration file. + /// Path to a Global configuration file. If null, the default path for a Global configuration file will be probed. + /// Path to a XDG configuration file. If null, the default path for a XDG configuration file will be probed. + /// Path to a System configuration file. If null, the default path for a System configuration file will be probed. + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation, + string systemConfigurationFileLocation) + { + return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation); } /// @@ -88,8 +208,8 @@ public Configuration(string globalConfigurationFileLocation = null, string xdgCo /// public virtual bool HasConfig(ConfigurationLevel level) { - using (ConfigurationSafeHandle snapshot = Snapshot ()) - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { return handle != null; } @@ -109,18 +229,51 @@ public void Dispose() #endregion + /// + /// Unset a configuration variable (key and value) in the local configuration. + /// + /// The key to unset. + public virtual bool Unset(string key) + { + return Unset(key, ConfigurationLevel.Local); + } + /// /// Unset a configuration variable (key and value). /// /// The key to unset. /// The configuration file which should be considered as the target of this operation - public virtual void Unset(string key, ConfigurationLevel level = ConfigurationLevel.Local) + public virtual bool Unset(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete(h, key); + return Proxy.git_config_delete(h, key); + } + } + + /// + /// Unset all configuration values in a multivar variable (key and value) in the local configuration. + /// + /// The key to unset. + public virtual bool UnsetAll(string key) + { + return UnsetAll(key, ConfigurationLevel.Local); + } + + /// + /// Unset all configuration values in a multivar variable (key and value). + /// + /// The key to unset. + /// The configuration file which should be considered as the target of this operation + public virtual bool UnsetAll(string key, ConfigurationLevel level) + { + Ensure.ArgumentNotNullOrEmptyString(key, "key"); + + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + { + return Proxy.git_config_delete_multivar(h, key); } } @@ -132,6 +285,64 @@ protected virtual void Dispose(bool disposing) configHandle.SafeDispose(); } + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [core] + /// bare = true + /// + /// + /// You would call: + /// + /// + /// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }).Value; + /// + /// + /// + /// The configuration value type + /// The key parts + /// The , or null if not set + public virtual ConfigurationEntry Get(string[] keyParts) + { + Ensure.ArgumentNotNull(keyParts, "keyParts"); + + return Get(string.Join(".", keyParts)); + } + + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [difftool "kdiff3"] + /// path = c:/Program Files/KDiff3/kdiff3.exe + /// + /// + /// You would call: + /// + /// + /// string where = repo.Config.Get<string>("difftool", "kdiff3", "path").Value; + /// + /// + /// + /// The configuration value type + /// The first key part + /// The second key part + /// The third key part + /// The , or null if not set + public virtual ConfigurationEntry Get(string firstKeyPart, string secondKeyPart, string thirdKeyPart) + { + Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); + Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); + Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "thirdKeyPart"); + + return Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }); + } + /// /// Get a configuration value for a key. Keys are in the form 'section.name'. /// @@ -165,7 +376,7 @@ public virtual ConfigurationEntry Get(string key) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle snapshot = Snapshot()) + using (ConfigurationHandle snapshot = Snapshot()) { return Proxy.git_config_get_entry(snapshot, key); } @@ -196,8 +407,8 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle snapshot = Snapshot()) - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { if (handle == null) { @@ -208,6 +419,202 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level } } + /// + /// Get a configuration value for the given key. + /// + /// The configuration value type. + /// The key + /// The configuration value, or the default value for the selected if not found + public virtual T GetValueOrDefault(string key) + { + return ValueOrDefault(Get(key), default(T)); + } + + /// + /// Get a configuration value for the given key, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key + /// The default value if the key is not set. + /// The configuration value, or the default value + public virtual T GetValueOrDefault(string key, T defaultValue) + { + return ValueOrDefault(Get(key), defaultValue); + } + + /// + /// Get a configuration value for the given key + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The configuration value, or the default value for if not found + public virtual T GetValueOrDefault(string key, ConfigurationLevel level) + { + return ValueOrDefault(Get(key, level), default(T)); + } + + /// + /// Get a configuration value for the given key, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or the default value. + public virtual T GetValueOrDefault(string key, ConfigurationLevel level, T defaultValue) + { + return ValueOrDefault(Get(key, level), defaultValue); + } + + /// + /// Get a configuration value for the given key parts + /// + /// The configuration value type. + /// The key parts. + /// The configuration value, or the default value for if not found + public virtual T GetValueOrDefault(string[] keyParts) + { + return ValueOrDefault(Get(keyParts), default(T)); + } + + /// + /// Get a configuration value for the given key parts, + /// or if the key is not set. + /// + /// The configuration value type. + /// The key parts. + /// The default value if the key is not set. + /// The configuration value, or the default value. + public virtual T GetValueOrDefault(string[] keyParts, T defaultValue) + { + return ValueOrDefault(Get(keyParts), defaultValue); + } + + /// + /// Get a configuration value for the given key parts. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The configuration value, or the default value for the selected if not found + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), default(T)); + } + + /// + /// Get a configuration value for the given key parts, + /// or if the key is not set. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The default value if the key is not set. + /// The configuration value, or the default. + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValue); + } + + /// + /// Get a configuration value for the given key, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string key, Func defaultValueSelector) + { + return ValueOrDefault(Get(key), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key. + /// The configuration file into which the key should be searched for. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string key, ConfigurationLevel level, Func defaultValueSelector) + { + return ValueOrDefault(Get(key, level), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key parts, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The key parts. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string[] keyParts, Func defaultValueSelector) + { + return ValueOrDefault(Get(keyParts), defaultValueSelector); + } + + /// + /// Get a configuration value for the given key parts, + /// or a value generated by + /// if the key is not set. + /// + /// The configuration value type. + /// The first key part. + /// The second key part. + /// The third key part. + /// The selector used to generate a default value if the key is not set. + /// The configuration value, or a generated default. + public virtual T GetValueOrDefault(string firstKeyPart, string secondKeyPart, string thirdKeyPart, Func defaultValueSelector) + { + return ValueOrDefault(Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValueSelector); + } + + private static T ValueOrDefault(ConfigurationEntry value, T defaultValue) + { + return value == null ? defaultValue : value.Value; + } + + private static T ValueOrDefault(ConfigurationEntry value, Func defaultValueSelector) + { + Ensure.ArgumentNotNull(defaultValueSelector, "defaultValueSelector"); + + return value == null + ? defaultValueSelector() + : value.Value; + } + + /// + /// Set a configuration value for a key in the local configuration. Keys are in the form 'section.name'. + /// + /// For example in order to set the value for this in a .git\config file: + /// + /// [test] + /// boolsetting = true + /// + /// You would call: + /// + /// repo.Config.Set("test.boolsetting", true); + /// + /// + /// The configuration value type + /// The key parts + /// The value + public virtual void Set(string key, T value) + { + Set(key, value, ConfigurationLevel.Local); + } + /// /// Set a configuration value for a key. Keys are in the form 'section.name'. /// @@ -225,11 +632,12 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level /// The key parts /// The value /// The configuration file which should be considered as the target of this operation - public virtual void Set(string key, T value, ConfigurationLevel level = ConfigurationLevel.Local) + public virtual void Set(string key, T value, ConfigurationLevel level) { + Ensure.ArgumentNotNull(value, "value"); Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { if (!configurationTypedUpdater.ContainsKey(typeof(T))) { @@ -240,27 +648,83 @@ public virtual void Set(string key, T value, ConfigurationLevel level = Confi } } + /// + /// Adds a configuration value for a multivalue key in the local configuration. Keys are in the form 'section.name'. + /// + /// For example in order to add the value for this in a .git\config file: + /// + /// [test] + /// plugin = first + /// + /// You would call: + /// + /// repo.Config.Add("test.plugin", "first"); + /// + /// + /// The key parts + /// The value + public virtual void Add(string key, string value) + { + Add(key, value, ConfigurationLevel.Local); + } + + /// + /// Adds a configuration value for a multivalue key. Keys are in the form 'section.name'. + /// + /// For example in order to add the value for this in a .git\config file: + /// + /// [test] + /// plugin = first + /// + /// You would call: + /// + /// repo.Config.Add("test.plugin", "first"); + /// + /// + /// The key parts + /// The value + /// The configuration file which should be considered as the target of this operation + public virtual void Add(string key, string value, ConfigurationLevel level) + { + Ensure.ArgumentNotNull(value, "value"); + Ensure.ArgumentNotNullOrEmptyString(key, "key"); + + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) + { + Proxy.git_config_add_string(h, key, value); + } + } + + /// + /// Find configuration entries matching . + /// + /// A regular expression. + /// Matching entries. + public virtual IEnumerable> Find(string regexp) + { + return Find(regexp, ConfigurationLevel.Local); + } + /// /// Find configuration entries matching . /// /// A regular expression. /// The configuration file into which the key should be searched for. /// Matching entries. - public virtual IEnumerable> Find(string regexp, - ConfigurationLevel level = ConfigurationLevel.Local) + public virtual IEnumerable> Find(string regexp, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(regexp, "regexp"); - using (ConfigurationSafeHandle snapshot = Snapshot()) - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true, snapshot)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, snapshot)) { - return Proxy.git_config_iterator_glob(h, regexp, BuildConfigEntry).ToList(); + return Proxy.git_config_iterator_glob(h, regexp).ToList(); } } - private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationSafeHandle fromHandle) + private ConfigurationHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationHandle fromHandle) { - ConfigurationSafeHandle handle = null; + ConfigurationHandle handle = null; if (fromHandle != null) { handle = Proxy.git_config_open_level(fromHandle, level); @@ -268,20 +732,19 @@ private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel l if (handle == null && throwIfStoreHasNotBeenFound) { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, "No {0} configuration file has been found.", - Enum.GetName(typeof(ConfigurationLevel), level))); + throw new LibGit2SharpException("No {0} configuration file has been found.", + Enum.GetName(typeof(ConfigurationLevel), level)); } return handle; } - private static Action GetUpdater(Action setter) + private static Action GetUpdater(Action setter) { return (key, val, handle) => setter(handle, key, (T)val); } - private readonly static IDictionary> configurationTypedUpdater = new Dictionary> + private readonly static IDictionary> configurationTypedUpdater = new Dictionary> { { typeof(int), GetUpdater(Proxy.git_config_set_int32) }, { typeof(long), GetUpdater(Proxy.git_config_set_int64) }, @@ -298,7 +761,7 @@ public virtual IEnumerator> GetEnumerator() return BuildConfigEntries().GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable>)this).GetEnumerator(); } @@ -308,22 +771,17 @@ private IEnumerable> BuildConfigEntries() return Proxy.git_config_foreach(configHandle, BuildConfigEntry); } - private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) + internal static unsafe ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) { - var entry = entryPtr.MarshalAs(); - - return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr), - LaxUtf8Marshaler.FromNative(entry.valuePtr), - (ConfigurationLevel)entry.level); + var entry = (GitConfigEntry*)entryPtr.ToPointer(); + return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry->namePtr), + LaxUtf8Marshaler.FromNative(entry->valuePtr), + (ConfigurationLevel)entry->level); } /// - /// Builds a based on current configuration. - /// - /// Name is populated from the user.name setting, and is "unknown" if unspecified. - /// Email is populated from the user.email setting, and is built from - /// and if unspecified. - /// + /// Builds a based on current configuration. If it is not found or + /// some configuration is missing, null is returned. /// /// The same escalation logic than in git.git will be used when looking for the key in the config files: /// - local: the Git file in the current repository @@ -333,43 +791,59 @@ private static ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) /// /// /// The timestamp to use for the . - /// The signature. + /// The signature or null if no user identity can be found in the configuration. public virtual Signature BuildSignature(DateTimeOffset now) { - return BuildSignature(now, false); + var name = this.GetValueOrDefault("user.name"); + var email = this.GetValueOrDefault("user.email"); + + if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(email)) + { + return null; + } + + return new Signature(name, email, now); } - internal Signature BuildSignature(DateTimeOffset now, bool shouldThrowIfNotFound) + internal Signature BuildSignatureOrThrow(DateTimeOffset now) { - var name = Get("user.name"); - var email = Get("user.email"); - - if (shouldThrowIfNotFound) + var signature = BuildSignature(now); + if (signature == null) { - if (name == null || string.IsNullOrEmpty(name.Value)) - { - throw new LibGit2SharpException( - "Can not find Name setting of the current user in Git configuration."); - } - - if (email == null || string.IsNullOrEmpty(email.Value)) - { - throw new LibGit2SharpException( - "Can not find Email setting of the current user in Git configuration."); - } + throw new LibGit2SharpException("This overload requires 'user.name' and 'user.email' to be set. " + + "Use a different overload or set those variables in the configuation"); } - var nameForSignature = name == null || string.IsNullOrEmpty(name.Value) ? "unknown" : name.Value; - var emailForSignature = email == null || string.IsNullOrEmpty(email.Value) - ? string.Format("{0}@{1}", Environment.UserName, Environment.UserDomainName) - : email.Value; - - return new Signature(nameForSignature, emailForSignature, now); + return signature; } - private ConfigurationSafeHandle Snapshot() + private ConfigurationHandle Snapshot() { return Proxy.git_config_snapshot(configHandle); } + + /// + /// Perform a series of actions within a transaction. + /// + /// The configuration will be locked during this function and the changes will be committed at the end. These + /// changes will not be visible in the configuration until the end of this method. + /// + /// If the action throws an exception, the changes will be rolled back. + /// + /// The code to run under the transaction + public virtual unsafe void WithinTransaction(Action action) + { + IntPtr txn = IntPtr.Zero; + try + { + txn = Proxy.git_config_lock(configHandle); + action(); + Proxy.git_transaction_commit(txn); + } + finally + { + Proxy.git_transaction_free(txn); + } + } } } diff --git a/LibGit2Sharp/ConfigurationEntry.cs b/LibGit2Sharp/ConfigurationEntry.cs index 7456be3c4..13c153a2a 100644 --- a/LibGit2Sharp/ConfigurationEntry.cs +++ b/LibGit2Sharp/ConfigurationEntry.cs @@ -48,8 +48,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} = \"{1}\"", Key, Value); + return string.Format(CultureInfo.InvariantCulture, "{0} = \"{1}\"", Key, Value); } } } diff --git a/LibGit2Sharp/ConfigurationExtensions.cs b/LibGit2Sharp/ConfigurationExtensions.cs deleted file mode 100644 index 5e79a2898..000000000 --- a/LibGit2Sharp/ConfigurationExtensions.cs +++ /dev/null @@ -1,207 +0,0 @@ -using System; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ConfigurationExtensions - { - /// - /// Get a configuration value for the given key parts. - /// - /// For example in order to get the value for this in a .git\config file: - /// - /// - /// [core] - /// bare = true - /// - /// - /// You would call: - /// - /// - /// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }).Value; - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The key parts - /// The , or null if not set - public static ConfigurationEntry Get(this Configuration config, string[] keyParts) - { - Ensure.ArgumentNotNull(keyParts, "keyParts"); - - return config.Get(string.Join(".", keyParts)); - } - - /// - /// Get a configuration value for the given key parts. - /// - /// For example in order to get the value for this in a .git\config file: - /// - /// - /// [difftool "kdiff3"] - /// path = c:/Program Files/KDiff3/kdiff3.exe - /// - /// - /// You would call: - /// - /// - /// string where = repo.Config.Get<string>("difftool", "kdiff3", "path").Value; - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The first key part - /// The second key part - /// The third key part - /// The , or null if not set - public static ConfigurationEntry Get(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart) - { - Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "thirdKeyPart"); - - return config.Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }); - } - - /// - /// Get a configuration value for the given key, - /// or if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key - /// The default value if the key is not set. - /// The configuration value, or the default. - public static T GetValueOrDefault(this Configuration config, string key, T defaultValue = default(T)) - { - return ValueOrDefault(config.Get(key), defaultValue); - } - - /// - /// Get a configuration value for the given key, - /// or if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key. - /// The configuration file into which the key should be searched for. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or the default. - public static T GetValueOrDefault(this Configuration config, string key, ConfigurationLevel level, T defaultValue = default(T)) - { - return ValueOrDefault(config.Get(key, level), defaultValue); - } - - /// - /// Get a configuration value for the given key parts, - /// or if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key parts. - /// The default value if the key is not set. - /// The configuration value, or the default. - public static T GetValueOrDefault(this Configuration config, string[] keyParts, T defaultValue = default(T)) - { - return ValueOrDefault(config.Get(keyParts), defaultValue); - } - - /// - /// Get a configuration value for the given key parts, - /// or if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The first key part. - /// The second key part. - /// The third key part. - /// The default value if the key is not set. - /// The configuration value, or the default. - public static T GetValueOrDefault(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue = default(T)) - { - return ValueOrDefault(config.Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValue); - } - - /// - /// Get a configuration value for the given key, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string key, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(key), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key. - /// The configuration file into which the key should be searched for. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string key, ConfigurationLevel level, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(key, level), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key parts, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The key parts. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string[] keyParts, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(keyParts), defaultValueSelector); - } - - /// - /// Get a configuration value for the given key parts, - /// or a value generated by - /// if the key is not set. - /// - /// The configuration value type. - /// The configuration being worked with. - /// The first key part. - /// The second key part. - /// The third key part. - /// The selector used to generate a default value if the key is not set. - /// The configuration value, or a generated default. - public static T GetValueOrDefault(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart, Func defaultValueSelector) - { - return ValueOrDefault(config.Get(firstKeyPart, secondKeyPart, thirdKeyPart), defaultValueSelector); - } - - private static T ValueOrDefault(ConfigurationEntry value, T defaultValue) - { - return value == null ? defaultValue : value.Value; - } - - private static T ValueOrDefault(ConfigurationEntry value, Func defaultValueSelector) - { - Ensure.ArgumentNotNull(defaultValueSelector, "defaultValueSelector"); - - return value == null - ? defaultValueSelector() - : value.Value; - } - } -} diff --git a/LibGit2Sharp/ConfigurationLevel.cs b/LibGit2Sharp/ConfigurationLevel.cs index b8ab12097..f0971a1c1 100644 --- a/LibGit2Sharp/ConfigurationLevel.cs +++ b/LibGit2Sharp/ConfigurationLevel.cs @@ -5,24 +5,34 @@ /// public enum ConfigurationLevel { + /// + /// Worktree specific configuration file; $GIT_DIR/config.worktree + /// + Worktree = 6, + /// /// The local .git/config of the current repository. /// - Local = 4, + Local = 5, /// /// The global ~/.gitconfig of the current user. /// - Global = 3, + Global = 4, /// /// The global ~/.config/git/config of the current user. /// - Xdg = 2, + Xdg = 3, /// /// The system wide .gitconfig. /// - System = 1, + System = 2, + + /// + /// Another system-wide configuration on Windows. + /// + ProgramData = 1, } } diff --git a/LibGit2Sharp/Conflict.cs b/LibGit2Sharp/Conflict.cs index 252535af1..705f66d15 100644 --- a/LibGit2Sharp/Conflict.cs +++ b/LibGit2Sharp/Conflict.cs @@ -61,12 +61,12 @@ public virtual IndexEntry Theirs } /// - /// Determines whether the specified is + /// Determines whether the specified is /// equal to the current . /// - /// The to compare with + /// The to compare with /// the current . - /// true if the specified is equal + /// true if the specified is equal /// to the current ; otherwise, /// false. public override bool Equals(object obj) diff --git a/LibGit2Sharp/ConflictCollection.cs b/LibGit2Sharp/ConflictCollection.cs index ba8bb8c37..90d48fa33 100644 --- a/LibGit2Sharp/ConflictCollection.cs +++ b/LibGit2Sharp/ConflictCollection.cs @@ -13,7 +13,7 @@ namespace LibGit2Sharp /// public class ConflictCollection : IEnumerable { - private readonly Repository repo; + private readonly Index index; /// /// Needed for mocking purposes. @@ -21,9 +21,9 @@ public class ConflictCollection : IEnumerable protected ConflictCollection() { } - internal ConflictCollection(Repository repo) + internal ConflictCollection(Index index) { - this.repo = repo; + this.index = index; } /// @@ -36,7 +36,7 @@ public virtual Conflict this[string path] { get { - return Proxy.git_index_conflict_get(repo.Index.Handle, repo, path); + return Proxy.git_index_conflict_get(index.Handle, path); } } @@ -48,7 +48,7 @@ public virtual IndexReucEntryCollection ResolvedConflicts { get { - return new IndexReucEntryCollection(repo); + return new IndexReucEntryCollection(index); } } @@ -60,7 +60,7 @@ public virtual IndexNameEntryCollection Names { get { - return new IndexNameEntryCollection(repo); + return new IndexNameEntryCollection(index); } } @@ -72,7 +72,7 @@ private List AllConflicts() IndexEntry ancestor = null, ours = null, theirs = null; string currentPath = null; - foreach (IndexEntry entry in repo.Index) + foreach (IndexEntry entry in index) { if (entry.StageLevel == StageLevel.Staged) { @@ -102,10 +102,10 @@ private List AllConflicts() theirs = entry; break; default: - throw new InvalidOperationException(string.Format( - CultureInfo.InvariantCulture, - "Entry '{0}' bears an unexpected StageLevel '{1}'", - entry.Path, entry.StageLevel)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected StageLevel '{1}'", + entry.Path, + entry.StageLevel)); } } diff --git a/LibGit2Sharp/ContentChanges.cs b/LibGit2Sharp/ContentChanges.cs index e5d84503e..c4628f919 100644 --- a/LibGit2Sharp/ContentChanges.cs +++ b/LibGit2Sharp/ContentChanges.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Text; @@ -20,12 +21,15 @@ public class ContentChanges protected ContentChanges() { } - internal ContentChanges(Repository repo, Blob oldBlob, Blob newBlob, GitDiffOptions options) + internal unsafe ContentChanges(Repository repo, Blob oldBlob, Blob newBlob, GitDiffOptions options) { Proxy.git_diff_blobs(repo.Handle, oldBlob != null ? oldBlob.Id : null, newBlob != null ? newBlob.Id : null, - options, FileCallback, HunkCallback, LineCallback); + options, + FileCallback, + HunkCallback, + LineCallback); } internal ContentChanges(bool isBinaryComparison) @@ -48,6 +52,16 @@ internal void AppendToPatch(string patch) /// public virtual int LinesDeleted { get; internal set; } + /// + /// The list of added lines. + /// + public virtual List AddedLines { get; } = new List(); + + /// + /// The list of deleted lines. + /// + public virtual List DeletedLines { get; } = new List(); + /// /// The patch corresponding to these changes. /// @@ -61,9 +75,9 @@ public virtual string Patch /// public virtual bool IsBinaryComparison { get; private set; } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) + private unsafe int FileCallback(git_diff_delta* delta, float progress, IntPtr payload) { - IsBinaryComparison = delta.IsBinary(); + IsBinaryComparison = delta->flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY); if (!IsBinaryComparison) { @@ -75,7 +89,7 @@ private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) return 0; } - private int HunkCallback(GitDiffDelta delta, GitDiffHunk hunk, IntPtr payload) + private unsafe int HunkCallback(git_diff_delta* delta, GitDiffHunk hunk, IntPtr payload) { string decodedContent = LaxUtf8Marshaler.FromBuffer(hunk.Header, (int)hunk.HeaderLen); @@ -83,7 +97,7 @@ private int HunkCallback(GitDiffDelta delta, GitDiffHunk hunk, IntPtr payload) return 0; } - private int LineCallback(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) + private unsafe int LineCallback(git_diff_delta* delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { string decodedContent = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); @@ -92,11 +106,13 @@ private int LineCallback(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, switch (line.lineOrigin) { case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: + AddedLines.Add(new Line(line.NewLineNo, decodedContent)); LinesAdded++; prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION: + DeletedLines.Add(new Line(line.OldLineNo, decodedContent)); LinesDeleted++; prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; @@ -120,7 +136,9 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - @"{{+{0}, -{1}}}", LinesAdded, LinesDeleted); + @"{{+{0}, -{1}}}", + LinesAdded, + LinesDeleted); } } } diff --git a/LibGit2Sharp/CopyNativeDependencies.targets b/LibGit2Sharp/CopyNativeDependencies.targets deleted file mode 100644 index 64f78d5b0..000000000 --- a/LibGit2Sharp/CopyNativeDependencies.targets +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - diff --git a/LibGit2Sharp/Core/ArrayMarshaler.cs b/LibGit2Sharp/Core/ArrayMarshaler.cs index b831f6dbc..4a37d241b 100644 --- a/LibGit2Sharp/Core/ArrayMarshaler.cs +++ b/LibGit2Sharp/Core/ArrayMarshaler.cs @@ -13,7 +13,7 @@ public ArrayMarshaler(T[] objs) for (var i = 0; i < objs.Length; i++) { - IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(T))); + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); ptrs[i] = ptr; Marshal.StructureToPtr(objs[i], ptr, false); } diff --git a/LibGit2Sharp/Core/EncodingMarshaler.cs b/LibGit2Sharp/Core/EncodingMarshaler.cs index ad85c5ec9..cb02c649b 100644 --- a/LibGit2Sharp/Core/EncodingMarshaler.cs +++ b/LibGit2Sharp/Core/EncodingMarshaler.cs @@ -32,7 +32,7 @@ public int GetNativeDataSize() return -1; } - public virtual IntPtr MarshalManagedToNative(Object managedObj) + public virtual IntPtr MarshalManagedToNative(object managedObj) { if (managedObj == null) { @@ -43,23 +43,24 @@ public virtual IntPtr MarshalManagedToNative(Object managedObj) if (str == null) { - throw new MarshalDirectiveException( - string.Format(CultureInfo.InvariantCulture, "{0} must be used on a string.", GetType().Name)); + throw new MarshalDirectiveException(string.Format(CultureInfo.InvariantCulture, + "{0} must be used on a string.", + GetType().Name)); } return FromManaged(encoding, str); } - public virtual Object MarshalNativeToManaged(IntPtr pNativeData) + public virtual object MarshalNativeToManaged(IntPtr pNativeData) { return FromNative(encoding, pNativeData); } #endregion - public static unsafe IntPtr FromManaged(Encoding encoding, String value) + public static unsafe IntPtr FromManaged(Encoding encoding, string value) { - if (value == null) + if (encoding == null || value == null) { return IntPtr.Zero; } @@ -92,7 +93,12 @@ public static void Cleanup(IntPtr pNativeData) public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData) { - if (pNativeData == IntPtr.Zero) + return FromNative(encoding, (byte*)pNativeData); + } + + public static unsafe string FromNative(Encoding encoding, byte* pNativeData) + { + if (pNativeData == null) { return null; } @@ -108,10 +114,10 @@ public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData) if (walk == start) { - return String.Empty; + return string.Empty; } - return new String((sbyte*)pNativeData.ToPointer(), 0, (int)(walk - start), encoding); + return new string((sbyte*)pNativeData, 0, (int)(walk - start), encoding); } public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData, int length) @@ -123,10 +129,10 @@ public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData, in if (length == 0) { - return String.Empty; + return string.Empty; } - return new String((sbyte*)pNativeData.ToPointer(), 0, length, encoding); + return new string((sbyte*)pNativeData.ToPointer(), 0, length, encoding); } public static string FromBuffer(Encoding encoding, byte[] buffer) @@ -154,7 +160,7 @@ public static string FromBuffer(Encoding encoding, byte[] buffer, int length) if (length == 0) { - return String.Empty; + return string.Empty; } return encoding.GetString(buffer, 0, length); diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index 5e7ea30b3..cd681e4ba 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -1,9 +1,8 @@ using System; -using System.Linq; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; namespace LibGit2Sharp.Core { @@ -35,7 +34,7 @@ public static void ArgumentNotNullOrEmptyEnumerable(IEnumerable argumentVa { ArgumentNotNull(argumentValue, argumentName); - if (argumentValue.Count() == 0) + if (!argumentValue.Any()) { throw new ArgumentException("Enumerable cannot be empty", argumentName); } @@ -50,7 +49,7 @@ public static void ArgumentNotNullOrEmptyString(string argumentValue, string arg { ArgumentNotNull(argumentValue, argumentName); - if (argumentValue.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(argumentValue)) { throw new ArgumentException("String cannot be empty", argumentName); } @@ -88,42 +87,71 @@ public static void ArgumentDoesNotContainZeroByte(string argumentValue, string a "Zero bytes ('\\0') are not allowed. A zero byte has been found at position {0}.", zeroPos), argumentName); } - private static readonly Dictionary> + /// + /// Checks an argument to ensure it isn't a IntPtr.Zero (aka null). + /// + /// The argument value to check. + /// The name of the argument. + public static void ArgumentNotZeroIntPtr(IntPtr argumentValue, string argumentName) + { + if (argumentValue == IntPtr.Zero) + { + throw new ArgumentNullException(argumentName); + } + } + + /// + /// Checks a pointer argument to ensure it is the expected pointer value. + /// + /// The argument value to check. + /// The expected value. + /// The name of the argument. + public static void ArgumentIsExpectedIntPtr(IntPtr argumentValue, IntPtr expectedValue, string argumentName) + { + if (argumentValue != expectedValue) + { + throw new ArgumentException("Unexpected IntPtr value", argumentName); + } + } + + private static readonly Dictionary> GitErrorsToLibGit2SharpExceptions = - new Dictionary> + new Dictionary> { - { GitErrorCode.User, (m, r, c) => new UserCancelledException(m, r, c) }, - { GitErrorCode.BareRepo, (m, r, c) => new BareRepositoryException(m, r, c) }, - { GitErrorCode.Exists, (m, r, c) => new NameConflictException(m, r, c) }, - { GitErrorCode.InvalidSpecification, (m, r, c) => new InvalidSpecificationException(m, r, c) }, - { GitErrorCode.UnmergedEntries, (m, r, c) => new UnmergedIndexEntriesException(m, r, c) }, - { GitErrorCode.NonFastForward, (m, r, c) => new NonFastForwardException(m, r, c) }, - { GitErrorCode.MergeConflict, (m, r, c) => new MergeConflictException(m, r, c) }, - { GitErrorCode.LockedFile, (m, r, c) => new LockedFileException(m, r, c) }, + { GitErrorCode.User, (m, c) => new UserCancelledException(m, c) }, + { GitErrorCode.BareRepo, (m, c) => new BareRepositoryException(m, c) }, + { GitErrorCode.Exists, (m, c) => new NameConflictException(m, c) }, + { GitErrorCode.InvalidSpecification, (m, c) => new InvalidSpecificationException(m, c) }, + { GitErrorCode.UnmergedEntries, (m, c) => new UnmergedIndexEntriesException(m, c) }, + { GitErrorCode.NonFastForward, (m, c) => new NonFastForwardException(m, c) }, + { GitErrorCode.Conflict, (m, c) => new CheckoutConflictException(m, c) }, + { GitErrorCode.LockedFile, (m, c) => new LockedFileException(m, c) }, + { GitErrorCode.NotFound, (m, c) => new NotFoundException(m, c) }, + { GitErrorCode.Peel, (m, c) => new PeelException(m, c) }, }; - private static void HandleError(int result) + private static unsafe void HandleError(int result) { string errorMessage; - GitError error = NativeMethods.giterr_last().MarshalAsGitError(); + GitErrorCategory errorCategory = GitErrorCategory.Unknown; + GitError* error = NativeMethods.git_error_last(); if (error == null) { - error = new GitError { Category = GitErrorCategory.Unknown, Message = IntPtr.Zero }; errorMessage = "No error message has been provided by the native library"; } else { - errorMessage = LaxUtf8Marshaler.FromNative(error.Message); + errorMessage = LaxUtf8Marshaler.FromNative(error->Message); } - Func exceptionBuilder; + Func exceptionBuilder; if (!GitErrorsToLibGit2SharpExceptions.TryGetValue((GitErrorCode)result, out exceptionBuilder)) { - exceptionBuilder = (m, r, c) => new LibGit2SharpException(m, r, c); + exceptionBuilder = (m, c) => new LibGit2SharpException(m, c); } - throw exceptionBuilder(errorMessage, (GitErrorCode)result, error.Category); + throw exceptionBuilder(errorMessage, errorCategory); } /// @@ -145,17 +173,15 @@ public static void ZeroResult(int result) } /// - /// Check that the result of a C call that returns a boolean value - /// was successful + /// Check that the result of a C call returns a boolean value. /// - /// The native function is expected to return strictly 0 for - /// success or a negative value in the case of failure. + /// The native function is expected to return strictly 0 or 1. /// /// /// The result to examine. public static void BooleanResult(int result) { - if (result == (int)GitErrorCode.Ok || result == 1) + if (result == 0 || result == 1) { return; } @@ -164,11 +190,11 @@ public static void BooleanResult(int result) } /// - /// Check that the result of a C call that returns an integer value - /// was successful + /// Check that the result of a C call that returns an integer + /// value was successful. /// - /// The native function is expected to return strictly 0 for - /// success or a negative value in the case of failure. + /// The native function is expected to return 0 or a positive + /// value for success or a negative value in the case of failure. /// /// /// The result to examine. @@ -182,16 +208,6 @@ public static void Int32Result(int result) HandleError(result); } - public static void NotNullResult(Object result) - { - if (result != null) - { - return; - } - - HandleError((int)GitErrorCode.Error); - } - /// /// Checks an argument by applying provided checker. /// @@ -208,35 +224,45 @@ public static void ArgumentConformsTo(T argumentValue, Func checker, throw new ArgumentException(argumentName); } - public static void GitObjectIsNotNull(GitObject gitObject, string identifier) + /// + /// Checks an argument is a positive integer. + /// + /// The argument value to check. + /// The name of the argument. + public static void ArgumentPositiveInt32(long argumentValue, string argumentName) { - Func exceptionBuilder; - - if (string.Equals("HEAD", identifier, StringComparison.Ordinal)) + if (argumentValue >= 0 && argumentValue <= uint.MaxValue) { - exceptionBuilder = m => new UnbornBranchException(m); - } - else - { - exceptionBuilder = m => new LibGit2SharpException(m); + return; } - GitObjectIsNotNull(gitObject, identifier, exceptionBuilder); + throw new ArgumentException(argumentName); } - public static void GitObjectIsNotNull( - GitObject gitObject, - string identifier, - Func exceptionBuilder) + /// + /// Check that the result of a C call that returns a non-null GitObject + /// using the default exception builder. + /// + /// The native function is expected to return a valid object value. + /// + /// + /// The to examine. + /// The identifier to examine. + public static void GitObjectIsNotNull(GitObject gitObject, string identifier) { if (gitObject != null) { return; } - throw exceptionBuilder(string.Format(CultureInfo.InvariantCulture, - "No valid git object identified by '{0}' exists in the repository.", - identifier)); + var messageFormat = "No valid git object identified by '{0}' exists in the repository."; + + if (string.Equals("HEAD", identifier, StringComparison.Ordinal)) + { + throw new UnbornBranchException(messageFormat, identifier); + } + + throw new NotFoundException(messageFormat, identifier); } } } diff --git a/LibGit2Sharp/Core/Epoch.cs b/LibGit2Sharp/Core/Epoch.cs deleted file mode 100644 index 963314d08..000000000 --- a/LibGit2Sharp/Core/Epoch.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core -{ - /// - /// Provides helper methods to help converting between Epoch (unix timestamp) and . - /// - public static class Epoch - { - private static readonly DateTimeOffset epochDateTimeOffset = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); - - /// - /// Builds a from a Unix timestamp and a timezone offset. - /// - /// The number of seconds since 00:00:00 UTC on 1 January 1970. - /// The number of minutes from UTC in a timezone. - /// A representing this instant. - public static DateTimeOffset ToDateTimeOffset(long secondsSinceEpoch, int timeZoneOffsetInMinutes) - { - DateTimeOffset utcDateTime = epochDateTimeOffset.AddSeconds(secondsSinceEpoch); - TimeSpan offset = TimeSpan.FromMinutes(timeZoneOffsetInMinutes); - return new DateTimeOffset(utcDateTime.DateTime.Add(offset), offset); - } - - /// - /// Converts the part of a into a Unix timestamp. - /// - /// The to convert. - /// The number of seconds since 00:00:00 UTC on 1 January 1970. - public static Int32 ToSecondsSinceEpoch(this DateTimeOffset date) - { - DateTimeOffset utcDate = date.ToUniversalTime(); - return (Int32)utcDate.Subtract(epochDateTimeOffset).TotalSeconds; - } - } -} diff --git a/LibGit2Sharp/Core/FetchPruneStrategy.cs b/LibGit2Sharp/Core/FetchPruneStrategy.cs new file mode 100644 index 000000000..695709421 --- /dev/null +++ b/LibGit2Sharp/Core/FetchPruneStrategy.cs @@ -0,0 +1,25 @@ +namespace LibGit2Sharp.Core +{ + /// + /// Specify how the remote tracking branches should be locally dealt with + /// when their upstream countepart doesn't exist anymore. + /// + internal enum FetchPruneStrategy + { + /// + /// Use the setting from the configuration + /// or, when there isn't any, fallback to default behavior. + /// + FromConfigurationOrDefault = 0, + + /// + /// Force pruning on + /// + Prune, + + /// + /// Force pruning off + /// + NoPrune, + } +} diff --git a/LibGit2Sharp/Core/FileHistory.cs b/LibGit2Sharp/Core/FileHistory.cs new file mode 100644 index 000000000..5775d0ab8 --- /dev/null +++ b/LibGit2Sharp/Core/FileHistory.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace LibGit2Sharp.Core +{ + /// + /// Represents a file-related log of commits beyond renames. + /// + internal class FileHistory : IEnumerable + { + #region Fields + + /// + /// The allowed commit sort strategies. + /// + private static readonly List AllowedSortStrategies = new List + { + CommitSortStrategies.Topological, + CommitSortStrategies.Time, + CommitSortStrategies.Topological | CommitSortStrategies.Time + }; + + /// + /// The repository. + /// + private readonly Repository _repo; + + /// + /// The file's path relative to the repository's root. + /// + private readonly string _path; + + /// + /// The filter to be used in querying the commit log. + /// + private readonly CommitFilter _queryFilter; + + #endregion + + #region Constructors + + /// + /// Initializes a new instance of the class. + /// The commits will be enumerated in reverse chronological order. + /// + /// The repository. + /// The file's path relative to the repository's root. + /// If any of the parameters is null. + internal FileHistory(Repository repo, string path) + : this(repo, path, new CommitFilter()) + { } + + /// + /// Initializes a new instance of the class. + /// The given instance specifies the commit + /// sort strategies and range of commits to be considered. + /// Only the time (corresponding to --date-order) and topological + /// (coresponding to --topo-order) sort strategies are supported. + /// + /// The repository. + /// The file's path relative to the repository's root. + /// The filter to be used in querying the commit log. + /// If any of the parameters is null. + /// When an unsupported commit sort strategy is specified. + internal FileHistory(Repository repo, string path, CommitFilter queryFilter) + { + Ensure.ArgumentNotNull(repo, "repo"); + Ensure.ArgumentNotNull(path, "path"); + Ensure.ArgumentNotNull(queryFilter, "queryFilter"); + + // Ensure the commit sort strategy makes sense. + if (!AllowedSortStrategies.Contains(queryFilter.SortBy)) + { + throw new ArgumentException("Unsupported sort strategy. Only 'Topological', 'Time', or 'Topological | Time' are allowed.", + nameof(queryFilter)); + } + + _repo = repo; + _path = path; + _queryFilter = queryFilter; + } + + #endregion + + #region IEnumerable Members + + /// + /// Gets the that enumerates the + /// instances representing the file's history, + /// including renames (as in git log --follow). + /// + /// A . + public IEnumerator GetEnumerator() + { + return FullHistory(_repo, _path, _queryFilter).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + #endregion + + /// + /// Gets the relevant commits in which the given file was created, changed, or renamed. + /// + /// The repository. + /// The file's path relative to the repository's root. + /// The filter to be used in querying the commits log. + /// A collection of instances. + private static IEnumerable FullHistory(IRepository repo, string path, CommitFilter filter) + { + var map = new Dictionary(); + + foreach (var currentCommit in repo.Commits.QueryBy(filter)) + { + var currentPath = map.Keys.Count > 0 ? map[currentCommit] : path; + var currentTreeEntry = currentCommit.Tree[currentPath]; + + if (currentTreeEntry == null) + { + yield break; + } + + var parentCount = currentCommit.Parents.Count(); + if (parentCount == 0) + { + yield return new LogEntry { Path = currentPath, Commit = currentCommit }; + } + else + { + DetermineParentPaths(repo, currentCommit, currentPath, map); + + if (parentCount != 1) + { + continue; + } + + var parentCommit = currentCommit.Parents.Single(); + var parentPath = map[parentCommit]; + var parentTreeEntry = parentCommit.Tree[parentPath]; + + if (parentTreeEntry == null || + parentTreeEntry.Target.Id != currentTreeEntry.Target.Id || + parentPath != currentPath) + { + yield return new LogEntry { Path = currentPath, Commit = currentCommit }; + } + } + } + } + + private static void DetermineParentPaths(IRepository repo, Commit currentCommit, string currentPath, IDictionary map) + { + foreach (var parentCommit in currentCommit.Parents.Where(parentCommit => !map.ContainsKey(parentCommit))) + { + map.Add(parentCommit, ParentPath(repo, currentCommit, currentPath, parentCommit)); + } + } + + private static string ParentPath(IRepository repo, Commit currentCommit, string currentPath, Commit parentCommit) + { + using (var treeChanges = repo.Diff.Compare(parentCommit.Tree, currentCommit.Tree)) + { + var treeEntryChanges = treeChanges.FirstOrDefault(c => c.Path == currentPath); + return treeEntryChanges != null && treeEntryChanges.Status == ChangeKind.Renamed + ? treeEntryChanges.OldPath + : currentPath; + } + } + } +} diff --git a/LibGit2Sharp/Core/FilePath.cs b/LibGit2Sharp/Core/FilePath.cs index 3d547aab2..580138cd0 100644 --- a/LibGit2Sharp/Core/FilePath.cs +++ b/LibGit2Sharp/Core/FilePath.cs @@ -60,7 +60,9 @@ private static string Replace(string path, char oldChar, char newChar) public bool Equals(FilePath other) { - return other == null ? posix == null : string.Equals(posix, other.posix, StringComparison.Ordinal); + return other == null + ? posix == null + : string.Equals(posix, other.posix, StringComparison.Ordinal); } public override bool Equals(object obj) diff --git a/LibGit2Sharp/Core/FilePathExtensions.cs b/LibGit2Sharp/Core/FilePathExtensions.cs index 930f38ed9..66118492c 100644 --- a/LibGit2Sharp/Core/FilePathExtensions.cs +++ b/LibGit2Sharp/Core/FilePathExtensions.cs @@ -2,14 +2,14 @@ namespace LibGit2Sharp.Core { internal static class FilePathExtensions { - internal static FilePath Combine(this FilePath @this, string childPath) + internal static FilePath Combine(this FilePath filePath, string childPath) { - return @this.IsNullOrEmpty() ? childPath : @this.Posix + "/" + childPath; + return filePath.IsNullOrEmpty() ? childPath : filePath.Posix + "/" + childPath; } - internal static bool IsNullOrEmpty(this FilePath @this) + internal static bool IsNullOrEmpty(this FilePath filePath) { - return ReferenceEquals(@this, null) || @this.Posix.Length == 0; + return ReferenceEquals(filePath, null) || filePath.Posix.Length == 0; } } -} \ No newline at end of file +} diff --git a/LibGit2Sharp/Core/FilePathMarshaler.cs b/LibGit2Sharp/Core/FilePathMarshaler.cs index 6b17df4cb..af6afb048 100644 --- a/LibGit2Sharp/Core/FilePathMarshaler.cs +++ b/LibGit2Sharp/Core/FilePathMarshaler.cs @@ -1,7 +1,6 @@ using System; using System.Globalization; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp.Core { @@ -19,7 +18,7 @@ internal class LaxFilePathNoCleanupMarshaler : LaxFilePathMarshaler { private static readonly LaxFilePathNoCleanupMarshaler staticInstance = new LaxFilePathNoCleanupMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -51,14 +50,14 @@ internal class StrictFilePathMarshaler : StrictUtf8Marshaler { private static readonly StrictFilePathMarshaler staticInstance = new StrictFilePathMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } #region ICustomMarshaler - public override IntPtr MarshalManagedToNative(Object managedObj) + public override IntPtr MarshalManagedToNative(object managedObj) { if (null == managedObj) { @@ -69,8 +68,9 @@ public override IntPtr MarshalManagedToNative(Object managedObj) if (null == filePath) { - throw new MarshalDirectiveException( - string.Format(CultureInfo.InvariantCulture, "{0} must be used on a FilePath.", GetType().Name)); + throw new MarshalDirectiveException(string.Format(CultureInfo.InvariantCulture, + "{0} must be used on a FilePath.", + this.GetType().Name)); } return FromManaged(filePath); @@ -98,14 +98,14 @@ internal class LaxFilePathMarshaler : LaxUtf8Marshaler { private static readonly LaxFilePathMarshaler staticInstance = new LaxFilePathMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } #region ICustomMarshaler - public override Object MarshalNativeToManaged(IntPtr pNativeData) + public override object MarshalNativeToManaged(IntPtr pNativeData) { return FromNative(pNativeData); } @@ -117,6 +117,11 @@ public override Object MarshalNativeToManaged(IntPtr pNativeData) return LaxUtf8Marshaler.FromNative(pNativeData); } + public new static unsafe FilePath FromNative(char* buffer) + { + return LaxUtf8Marshaler.FromNative(buffer); + } + public new static FilePath FromBuffer(byte[] buffer) { return LaxUtf8Marshaler.FromBuffer(buffer); diff --git a/LibGit2Sharp/Core/GitBlame.cs b/LibGit2Sharp/Core/GitBlame.cs index fd08b991c..d484b0b4b 100644 --- a/LibGit2Sharp/Core/GitBlame.cs +++ b/LibGit2Sharp/Core/GitBlame.cs @@ -37,36 +37,37 @@ internal enum GitBlameOptionFlags /// Restrict the search of commits to those reachable /// following only the first parents. /// - GIT_BLAME_FIRST_PARENT = (1<<4), + GIT_BLAME_FIRST_PARENT = (1 << 4), } [StructLayout(LayoutKind.Sequential)] - internal class GitBlameOptions + internal class git_blame_options { public uint version = 1; public GitBlameOptionFlags flags; - public UInt16 MinMatchCharacters; - public GitOid NewestCommit; - public GitOid OldestCommit; - public uint MinLine; - public uint MaxLine; + + public ushort min_match_characters; + public git_oid newest_commit; + public git_oid oldest_commit; + public UIntPtr min_line; + public UIntPtr max_line; } [StructLayout(LayoutKind.Sequential)] - internal class GitBlameHunk + internal unsafe struct git_blame_hunk { - public ushort LinesInHunk; + public UIntPtr lines_in_hunk; - public GitOid FinalCommitId; - public ushort FinalStartLineNumber; - public IntPtr FinalSignature; + public git_oid final_commit_id; + public UIntPtr final_start_line_number; + public git_signature* final_signature; - public GitOid OrigCommitId; - public IntPtr OrigPath; - public ushort OrigStartLineNumber; - public IntPtr OrigSignature; + public git_oid orig_commit_id; + public char* orig_path; + public UIntPtr orig_start_line_number; + public git_signature* orig_signature; - public byte Boundary; + public byte boundary; } internal static class BlameStrategyExtensions @@ -79,8 +80,9 @@ public static GitBlameOptionFlags ToGitBlameOptionFlags(this BlameStrategy strat return GitBlameOptionFlags.GIT_BLAME_NORMAL; default: - throw new NotSupportedException( - string.Format(CultureInfo.InvariantCulture, "{0} is not supported at this time", strategy)); + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, + "{0} is not supported at this time", + strategy)); } } } diff --git a/LibGit2Sharp/Core/GitBuf.cs b/LibGit2Sharp/Core/GitBuf.cs index 09860fdc3..19b1328b9 100644 --- a/LibGit2Sharp/Core/GitBuf.cs +++ b/LibGit2Sharp/Core/GitBuf.cs @@ -12,7 +12,7 @@ internal class GitBuf : IDisposable public void Dispose() { - Proxy.git_buf_free(this); + Proxy.git_buf_dispose(this); } } } diff --git a/LibGit2Sharp/Core/GitCertificate.cs b/LibGit2Sharp/Core/GitCertificate.cs new file mode 100644 index 000000000..9b0eafd1c --- /dev/null +++ b/LibGit2Sharp/Core/GitCertificate.cs @@ -0,0 +1,10 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct git_certificate + { + public GitCertificateType type; + } +} diff --git a/LibGit2Sharp/Core/GitCertificateSsh.cs b/LibGit2Sharp/Core/GitCertificateSsh.cs new file mode 100644 index 000000000..e3e7c4927 --- /dev/null +++ b/LibGit2Sharp/Core/GitCertificateSsh.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_certificate_ssh + { + public GitCertificateType cert_type; + public GitCertificateSshType type; + + /// + /// The MD5 hash (if appropriate) + /// + public unsafe fixed byte HashMD5[16]; + + /// + /// The SHA1 hash (if appropriate) + /// + public unsafe fixed byte HashSHA1[20]; + } +} diff --git a/LibGit2Sharp/Core/GitCertificateSshType.cs b/LibGit2Sharp/Core/GitCertificateSshType.cs new file mode 100644 index 000000000..4fc432e9a --- /dev/null +++ b/LibGit2Sharp/Core/GitCertificateSshType.cs @@ -0,0 +1,11 @@ +using System; + +namespace LibGit2Sharp.Core +{ + [Flags] + internal enum GitCertificateSshType + { + MD5 = (1 << 0), + SHA1 = (1 << 1), + } +} diff --git a/LibGit2Sharp/Core/GitCertificateType.cs b/LibGit2Sharp/Core/GitCertificateType.cs new file mode 100644 index 000000000..1b06b1af3 --- /dev/null +++ b/LibGit2Sharp/Core/GitCertificateType.cs @@ -0,0 +1,29 @@ +namespace LibGit2Sharp.Core +{ + /// + /// Git certificate types to present to the user + /// + internal enum GitCertificateType + { + /// + /// No information about the certificate is available. + /// + None = 0, + + /// + /// The certificate is a x509 certificate + /// + X509 = 1, + + /// + /// The "certificate" is in fact a hostkey identification for ssh. + /// + Hostkey = 2, + + /// + /// The "certificate" is in fact a collection of `name:content` strings + /// containing information about the certificate. + /// + StrArray = 3, + } +} diff --git a/LibGit2Sharp/Core/GitCertificateX509.cs b/LibGit2Sharp/Core/GitCertificateX509.cs new file mode 100644 index 000000000..2ed13b74c --- /dev/null +++ b/LibGit2Sharp/Core/GitCertificateX509.cs @@ -0,0 +1,22 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_certificate_x509 + { + /// + /// Type of the certificate, in this case, GitCertificateType.X509 + /// + public GitCertificateType cert_type; + /// + /// Pointer to the X509 certificate data + /// + public byte* data; + /// + /// The size of the certificate data + /// + public UIntPtr len; + } +} diff --git a/LibGit2Sharp/Core/GitCheckoutOpts.cs b/LibGit2Sharp/Core/GitCheckoutOpts.cs index dfedca458..053258565 100644 --- a/LibGit2Sharp/Core/GitCheckoutOpts.cs +++ b/LibGit2Sharp/Core/GitCheckoutOpts.cs @@ -17,14 +17,14 @@ internal enum CheckoutStrategy GIT_CHECKOUT_SAFE = (1 << 0), /// - /// Allow safe updates plus creation of missing files. + /// Allow update of entries in working dir that are modified from HEAD. /// - GIT_CHECKOUT_SAFE_CREATE = (1 << 1), + GIT_CHECKOUT_FORCE = (1 << 1), /// - /// Allow update of entries in working dir that are modified from HEAD. + /// Allow checkout to recreate missing files. /// - GIT_CHECKOUT_FORCE = (1 << 2), + GIT_CHECKOUT_RECREATE_MISSING = (1 << 2), /// /// Allow checkout to make safe updates even if conflicts are found @@ -48,6 +48,7 @@ internal enum CheckoutStrategy /// /// Normally checkout updates index entries as it goes; this stops that + /// Implies `GIT_CHECKOUT_DONT_WRITE_INDEX`. /// GIT_CHECKOUT_DONT_UPDATE_INDEX = (1 << 8), @@ -94,6 +95,18 @@ internal enum CheckoutStrategy /// GIT_CHECKOUT_CONFLICT_STYLE_DIFF3 = (1 << 21), + /// + /// Don't overwrite existing files or folders + /// + GIT_CHECKOUT_DONT_REMOVE_EXISTING = (1 << 22), + + /// + /// Normally checkout writes the index upon completion; this prevents that. + /// + GIT_CHECKOUT_DONT_WRITE_INDEX = (1 << 23), + + // THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED + /// /// Recursively checkout submodules with same options (NOT IMPLEMENTED) /// @@ -105,6 +118,7 @@ internal enum CheckoutStrategy GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1 << 17), } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int checkout_notify_cb( CheckoutNotifyFlags why, IntPtr path, @@ -113,12 +127,18 @@ internal delegate int checkout_notify_cb( IntPtr workdir, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate void progress_cb( IntPtr strPtr, UIntPtr completed_steps, UIntPtr total_steps, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int perfdata_cb( + IntPtr perfdata, + IntPtr payload); + [StructLayout(LayoutKind.Sequential)] internal struct GitCheckoutOpts { @@ -141,11 +161,15 @@ internal struct GitCheckoutOpts public GitStrArray paths; public IntPtr baseline; + public IntPtr baseline_index; public IntPtr target_directory; public IntPtr ancestor_label; public IntPtr our_label; public IntPtr their_label; + + public perfdata_cb perfdata_cb; + public IntPtr perfdata_payload; } /// diff --git a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs index 7fda697a4..0fba82754 100644 --- a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs +++ b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs @@ -1,7 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; namespace LibGit2Sharp.Core { @@ -69,12 +66,15 @@ internal static CheckoutStrategy CheckoutStrategyFromFileConflictStrategy(Checko case CheckoutFileConflictStrategy.Ours: flags = CheckoutStrategy.GIT_CHECKOUT_USE_OURS; break; + case CheckoutFileConflictStrategy.Theirs: flags = CheckoutStrategy.GIT_CHECKOUT_USE_THEIRS; break; + case CheckoutFileConflictStrategy.Merge: flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_MERGE; break; + case CheckoutFileConflictStrategy.Diff3: flags = CheckoutStrategy.GIT_CHECKOUT_CONFLICT_STYLE_DIFF3; break; diff --git a/LibGit2Sharp/Core/GitCherryPickOptions.cs b/LibGit2Sharp/Core/GitCherryPickOptions.cs index b06f22a7d..287ecacf0 100644 --- a/LibGit2Sharp/Core/GitCherryPickOptions.cs +++ b/LibGit2Sharp/Core/GitCherryPickOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { diff --git a/LibGit2Sharp/Core/GitCloneOptions.cs b/LibGit2Sharp/Core/GitCloneOptions.cs index 803bc8b3c..1ad86c5c3 100644 --- a/LibGit2Sharp/Core/GitCloneOptions.cs +++ b/LibGit2Sharp/Core/GitCloneOptions.cs @@ -17,14 +17,12 @@ internal struct GitCloneOptions public uint Version; public GitCheckoutOpts CheckoutOpts; - public GitRemoteCallbacks RemoteCallbacks; + public GitFetchOptions FetchOpts; public int Bare; public GitCloneLocal Local; public IntPtr CheckoutBranch; - public IntPtr signature; // Really a SignatureSafeHandle - public IntPtr RepositoryCb; public IntPtr RepositoryCbPayload; diff --git a/LibGit2Sharp/Core/GitConfigEntry.cs b/LibGit2Sharp/Core/GitConfigEntry.cs index d8bd3751f..7af657894 100644 --- a/LibGit2Sharp/Core/GitConfigEntry.cs +++ b/LibGit2Sharp/Core/GitConfigEntry.cs @@ -1,13 +1,16 @@ -using System; using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal struct GitConfigEntry + internal unsafe struct GitConfigEntry { - public IntPtr namePtr; - public IntPtr valuePtr; + public char* namePtr; + public char* valuePtr; + public char* backend_type; + public char* origin_path; + public uint include_depth; public uint level; + public void* freePtr; } } diff --git a/LibGit2Sharp/Core/GitCredential.cs b/LibGit2Sharp/Core/GitCredential.cs new file mode 100644 index 000000000..f3c6605fd --- /dev/null +++ b/LibGit2Sharp/Core/GitCredential.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitCredential + { + public GitCredentialType credtype; + public IntPtr free; + } +} + diff --git a/LibGit2Sharp/Core/GitCredentialType.cs b/LibGit2Sharp/Core/GitCredentialType.cs index 6ae636588..0ab1273e2 100644 --- a/LibGit2Sharp/Core/GitCredentialType.cs +++ b/LibGit2Sharp/Core/GitCredentialType.cs @@ -6,7 +6,7 @@ namespace LibGit2Sharp.Core /// Authentication type requested. /// [Flags] - public enum GitCredentialType + internal enum GitCredentialType { /// /// A plaintext username and password. @@ -32,5 +32,20 @@ public enum GitCredentialType /// TODO /// SshInteractive = (1 << 4), + + /// + /// Username-only information + /// + /// If the SSH transport does not know which username to use, + /// it will ask via this credential type. + /// + Username = (1 << 5), + + /// + /// Credentials read from memory. + /// + /// Only available for libssh2+OpenSSL for now. + /// + SshMemory = (1 << 6), } } diff --git a/LibGit2Sharp/Core/GitCredentialUserpass.cs b/LibGit2Sharp/Core/GitCredentialUserpass.cs new file mode 100644 index 000000000..ab6cf6c7a --- /dev/null +++ b/LibGit2Sharp/Core/GitCredentialUserpass.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct GitCredentialUserpass + { + public GitCredential parent; + public char* username; + public char* password; + } +} + diff --git a/LibGit2Sharp/Core/GitDescribeFormatOptions.cs b/LibGit2Sharp/Core/GitDescribeFormatOptions.cs new file mode 100644 index 000000000..58f0d16ad --- /dev/null +++ b/LibGit2Sharp/Core/GitDescribeFormatOptions.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitDescribeFormatOptions + { + public uint Version; + public uint MinAbbreviatedSize; + public bool AlwaysUseLongFormat; + public IntPtr DirtySuffix; + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitDescribeOptions + { + public uint Version; + public uint MaxCandidatesTags; + public DescribeStrategy DescribeStrategy; + public IntPtr Pattern; + public bool OnlyFollowFirstParent; + public bool ShowCommitOidAsFallback; + } +} diff --git a/LibGit2Sharp/Core/GitDiff.cs b/LibGit2Sharp/Core/GitDiff.cs index 27b5fab19..44679124d 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -80,6 +80,13 @@ internal enum GitDiffOptionFlags /// GIT_DIFF_IGNORE_CASE = (1 << 10), + + /// + /// May be combined with `GIT_DIFF_IGNORE_CASE` to specify that a file + /// that has changed case will be returned as an add/delete pair. + /// + GIT_DIFF_INCLUDE_CASECHANGE = (1 << 11), + /// /// If the pathspec is set in the diff options, this flags means to /// apply it as an exact match instead of as an fnmatch pattern. @@ -126,6 +133,13 @@ internal enum GitDiffOptionFlags * Options controlling how output will be generated */ + /// + /// Use a heuristic that takes indentation and whitespace into account + /// which generally can produce better diffs when dealing with ambiguous + /// diff hunks. + /// + GIT_DIFF_INDENT_HEURISTIC = (1 << 18), + /// /// Treat all files as text, disabling binary attributes and detection /// @@ -184,12 +198,20 @@ internal enum GitDiffOptionFlags GIT_DIFF_SHOW_BINARY = (1 << 30), } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int diff_notify_cb( IntPtr diff_so_far, IntPtr delta_to_add, IntPtr matched_pathspec, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int diff_progress_cb( + IntPtr diff_so_far, + IntPtr old_path, + IntPtr new_path, + IntPtr payload); + [StructLayout(LayoutKind.Sequential)] internal class GitDiffOptions : IDisposable { @@ -201,14 +223,15 @@ internal class GitDiffOptions : IDisposable public SubmoduleIgnore IgnoreSubmodules; public GitStrArrayManaged PathSpec; public diff_notify_cb NotifyCallback; - public IntPtr NotifyPayload; + public diff_progress_cb ProgressCallback; + public IntPtr Payload; /* options controlling how to diff text is generated */ - public ushort ContextLines; - public ushort InterhunkLines; + public uint ContextLines; + public uint InterhunkLines; public ushort IdAbbrev; - public Int64 MaxSize; + public long MaxSize; public IntPtr OldPrefixString; public IntPtr NewPrefixString; @@ -224,27 +247,29 @@ internal enum GitDiffFlags GIT_DIFF_FLAG_BINARY = (1 << 0), GIT_DIFF_FLAG_NOT_BINARY = (1 << 1), GIT_DIFF_FLAG_VALID_ID = (1 << 2), + GIT_DIFF_FLAG_EXISTS = (1 << 3), } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffFile + internal unsafe struct git_diff_file { - public GitOid Id; - public IntPtr Path; - public Int64 Size; + public git_oid Id; + public char* Path; + public long Size; public GitDiffFlags Flags; - public UInt16 Mode; + public ushort Mode; + public ushort IdAbbrev; } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffDelta + internal unsafe struct git_diff_delta { - public ChangeKind Status; - public GitDiffFlags Flags; - public UInt16 Similarity; - public UInt16 NumberOfFiles; - public GitDiffFile OldFile; - public GitDiffFile NewFile; + public ChangeKind status; + public GitDiffFlags flags; + public ushort similarity; + public ushort nfiles; + public git_diff_file old_file; + public git_diff_file new_file; } [StructLayout(LayoutKind.Sequential)] @@ -268,7 +293,7 @@ internal class GitDiffLine public int NewLineNo; public int NumLines; public UIntPtr contentLen; - public Int64 contentOffset; + public long contentOffset; public IntPtr content; } @@ -288,11 +313,11 @@ enum GitDiffLineOrigin : byte enum GitDiffFormat { - GIT_DIFF_FORMAT_PATCH = 1, // < full git diff + GIT_DIFF_FORMAT_PATCH = 1, // < full git diff GIT_DIFF_FORMAT_PATCH_HEADER = 2, // < just the file headers of patch - GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw - GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only - GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status + GIT_DIFF_FORMAT_RAW = 3, // < like git diff --raw + GIT_DIFF_FORMAT_NAME_ONLY = 4, // < like git diff --name-only + GIT_DIFF_FORMAT_NAME_STATUS = 5, // < like git diff --name-status } [Flags] @@ -346,13 +371,43 @@ internal class GitDiffFindOptions { public uint Version = 1; public GitDiffFindFlags Flags; - public UInt16 RenameThreshold; - public UInt16 RenameFromRewriteThreshold; - public UInt16 CopyThreshold; - public UInt16 BreakRewriteThreshold; + public ushort RenameThreshold; + public ushort RenameFromRewriteThreshold; + public ushort CopyThreshold; + public ushort BreakRewriteThreshold; public UIntPtr RenameLimit; // TODO public IntPtr SimilarityMetric; } + + [Flags] + enum GitDiffBinaryType + { + // There is no binary delta. + GIT_DIFF_BINARY_NONE = 0, + + // The binary data is the literal contents of the file. */ + GIT_DIFF_BINARY_LITERAL, + + // The binary data is the delta from one side to the other. */ + GIT_DIFF_BINARY_DELTA, + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffBinaryFile + { + public GitDiffBinaryType Type; + public IntPtr Data; + public UIntPtr DataLen; + public UIntPtr InflatedLen; + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffBinary + { + public uint ContainsData; + public GitDiffBinaryFile OldFile; + public GitDiffBinaryFile NewFile; + } } diff --git a/LibGit2Sharp/Core/GitDiffExtensions.cs b/LibGit2Sharp/Core/GitDiffExtensions.cs deleted file mode 100644 index a131d2091..000000000 --- a/LibGit2Sharp/Core/GitDiffExtensions.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace LibGit2Sharp.Core -{ - internal static class GitDiffExtensions - { - public static bool IsBinary(this GitDiffDelta delta) - { - return delta.Flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY); - } - } -} diff --git a/LibGit2Sharp/Core/GitError.cs b/LibGit2Sharp/Core/GitError.cs index 0041097da..3d982f466 100644 --- a/LibGit2Sharp/Core/GitError.cs +++ b/LibGit2Sharp/Core/GitError.cs @@ -4,9 +4,9 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitError + internal unsafe struct GitError { - public IntPtr Message; + public char* Message; public GitErrorCategory Category; } } diff --git a/LibGit2Sharp/Core/GitErrorCategory.cs b/LibGit2Sharp/Core/GitErrorCategory.cs index 66e10cbaf..5fc4c7d57 100644 --- a/LibGit2Sharp/Core/GitErrorCategory.cs +++ b/LibGit2Sharp/Core/GitErrorCategory.cs @@ -30,5 +30,12 @@ internal enum GitErrorCategory Filter, Revert, Callback, + CherryPick, + Describe, + Rebase, + Filesystem, + Patch, + Worktree, + Sha1 } } diff --git a/LibGit2Sharp/Core/GitErrorCode.cs b/LibGit2Sharp/Core/GitErrorCode.cs index 3837057b8..6180cc4a8 100644 --- a/LibGit2Sharp/Core/GitErrorCode.cs +++ b/LibGit2Sharp/Core/GitErrorCode.cs @@ -56,9 +56,10 @@ internal enum GitErrorCode InvalidSpecification = -12, /// - /// A conflicting change has been detected. + /// A conflicting change has been detected in the index + /// or working directory. /// - MergeConflict = -13, + Conflict = -13, /// /// A file operation failed because the file was locked. @@ -70,6 +71,51 @@ internal enum GitErrorCode /// Modified = -15, + /// + /// Authentication error. + /// + Auth = -16, + + /// + /// Server certificate is invalid. + /// + Certificate = -17, + + /// + /// Patch/merge has already been applied. + /// + Applied = -18, + + /// + /// The requested peel operation is not possible. + /// + Peel = -19, + + /// + /// Unexpected EOF. + /// + EndOfFile = -20, + + /// + /// Invalid operation or input. + /// + Invalid = -21, + + /// + /// Uncommitted changes in index prevented operation. + /// + Uncommitted = -22, + + /// + /// The operation is not valid for a directory. + /// + Directory = -23, + + /// + /// A merge conflict exists and cannot continue + /// + MergeConflict = -24, + /// /// Skip and passthrough the given ODB backend. /// @@ -79,5 +125,15 @@ internal enum GitErrorCode /// There are no more entries left to iterate. /// IterOver = -31, + + /// + /// Internal-only. + /// + Retry = -32, + + /// + /// A retrieved object did not match its expected ID. + /// + Mismatch = -33, } } diff --git a/LibGit2Sharp/Core/GitFetchOptions.cs b/LibGit2Sharp/Core/GitFetchOptions.cs new file mode 100644 index 000000000..26f4c8c7e --- /dev/null +++ b/LibGit2Sharp/Core/GitFetchOptions.cs @@ -0,0 +1,18 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitFetchOptions + { + public int Version = 1; + public GitRemoteCallbacks RemoteCallbacks; + public FetchPruneStrategy Prune; + public bool UpdateFetchHead = true; + public TagFetchMode download_tags; + public GitProxyOptions ProxyOptions; + public int Depth = 0; // GIT_FETCH_DEPTH_FULL + public RemoteRedirectMode FollowRedirects = RemoteRedirectMode.Initial; + public GitStrArrayManaged CustomHeaders; + } +} diff --git a/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs b/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs new file mode 100644 index 000000000..9c7f3952a --- /dev/null +++ b/LibGit2Sharp/Core/GitFetchOptionsWrapper.cs @@ -0,0 +1,37 @@ +using System; + +namespace LibGit2Sharp.Core +{ + /// + /// Git fetch options wrapper. Disposable wrapper for GitFetchOptions + /// + internal class GitFetchOptionsWrapper : IDisposable + { + public GitFetchOptionsWrapper() : this(new GitFetchOptions()) { } + + public GitFetchOptionsWrapper(GitFetchOptions fetchOptions) + { + Options = fetchOptions; + } + + public GitFetchOptions Options { get; private set; } + + #region IDisposable + private bool disposedValue = false; // To detect redundant calls + protected virtual void Dispose(bool disposing) + { + if (disposedValue) + return; + + Options.CustomHeaders.Dispose(); + EncodingMarshaler.Cleanup(Options.ProxyOptions.Url); + disposedValue = true; + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/LibGit2Sharp/Core/GitFilter.cs b/LibGit2Sharp/Core/GitFilter.cs new file mode 100644 index 000000000..72fa2f763 --- /dev/null +++ b/LibGit2Sharp/Core/GitFilter.cs @@ -0,0 +1,127 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /// + /// A git filter + /// + [StructLayout(LayoutKind.Sequential)] + internal class GitFilter + { + public uint version = 1; + + public IntPtr attributes; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_init_fn init; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_shutdown_fn shutdown; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_check_fn check; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_apply_fn apply; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_stream_fn stream; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public git_filter_cleanup_fn cleanup; + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + /// + /// Initialize callback on filter + /// + /// Specified as `filter.initialize`, this is an optional callback invoked + /// before a filter is first used. It will be called once at most. + /// + /// If non-NULL, the filter's `initialize` callback will be invoked right + /// 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). + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int git_filter_init_fn(IntPtr filter); + + /// + /// Shutdown callback on filter + /// + /// Specified as `filter.shutdown`, this is an optional callback invoked + /// when the filter is unregistered or when libgit2 is shutting down. It + /// will be called once at most and should release resources as needed. + /// Typically this function will free the `git_filter` object itself. + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void git_filter_shutdown_fn(IntPtr filter); + + /// + /// Callback to decide if a given source needs this filter + /// Specified as `filter.check`, this is an optional callback that checks if filtering is needed for a given source. + /// + /// It should return 0 if the filter should be applied (i.e. success), GIT_PASSTHROUGH if the filter should + /// not be applied, or an error code to fail out of the filter processing pipeline and return to the caller. + /// + /// The `attr_values` will be set to the values of any attributes given in the filter definition. See `git_filter` below for more detail. + /// + /// The `payload` will be a pointer to a reference payload for the filter. This will start as NULL, but `check` can assign to this + /// pointer for later use by the `apply` callback. Note that the value should be heap allocated (not stack), so that it doesn't go + /// away before the `apply` 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. + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int git_filter_check_fn( + GitFilter gitFilter, + IntPtr payload, + IntPtr filterSource, + IntPtr attributeValues); + + /// + /// Callback to actually perform the data filtering + /// + /// Specified as `filter.apply`, this is the callback that actually filters data. + /// If it successfully writes the output, it should return 0. Like `check`, + /// it can return GIT_PASSTHROUGH to indicate that the filter doesn't want to run. + /// Other error codes will stop filter processing and return to the caller. + /// + /// 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. + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int git_filter_apply_fn( + GitFilter gitFilter, + IntPtr payload, + IntPtr gitBufTo, + IntPtr gitBufFrom, + IntPtr filterSource); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int git_filter_stream_fn( + out IntPtr git_writestream_out, + GitFilter self, + IntPtr payload, + IntPtr filterSource, + IntPtr git_writestream_next); + + /// + /// Callback to clean up after filtering has been applied. Specified as `filter.cleanup`, this is an optional callback invoked + /// after the filter has been applied. If the `check` or `apply` callbacks allocated a `payload` + /// to keep per-source filter state, use this callback to free that payload and release resources as required. + /// + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void git_filter_cleanup_fn(IntPtr gitFilter, IntPtr payload); + } + /// + /// The file source being filtered + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_filter_source + { + public git_repository* repository; + + public char* path; + + public git_oid oid; + } +} diff --git a/LibGit2Sharp/Core/GitIndexEntry.cs b/LibGit2Sharp/Core/GitIndexEntry.cs index 51689fa17..ac0c141ed 100644 --- a/LibGit2Sharp/Core/GitIndexEntry.cs +++ b/LibGit2Sharp/Core/GitIndexEntry.cs @@ -4,19 +4,28 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexEntry + internal unsafe struct git_index_mtime { - public GitIndexTime CTime; - public GitIndexTime MTime; - public uint Dev; - public uint Ino; - public uint Mode; - public uint Uid; - public uint Gid; - public Int64 file_size; - public GitOid Id; - public ushort Flags; - public ushort ExtendedFlags; - public IntPtr Path; + public int seconds; + public uint nanoseconds; + } + + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_index_entry + { + internal const ushort GIT_IDXENTRY_VALID = 0x8000; + + public git_index_mtime ctime; + public git_index_mtime mtime; + public uint dev; + public uint ino; + public uint mode; + public uint uid; + public uint gid; + public uint file_size; + public git_oid id; + public ushort flags; + public ushort extended_flags; + public char* path; } } diff --git a/LibGit2Sharp/Core/GitIndexNameEntry.cs b/LibGit2Sharp/Core/GitIndexNameEntry.cs index 251a00791..47a9b1bcb 100644 --- a/LibGit2Sharp/Core/GitIndexNameEntry.cs +++ b/LibGit2Sharp/Core/GitIndexNameEntry.cs @@ -4,10 +4,10 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexNameEntry + internal unsafe struct git_index_name_entry { - public IntPtr Ancestor; - public IntPtr Ours; - public IntPtr Theirs; + public char* ancestor; + public char* ours; + public char* theirs; } } diff --git a/LibGit2Sharp/Core/GitIndexReucEntry.cs b/LibGit2Sharp/Core/GitIndexReucEntry.cs index dfd684edb..bc98d50df 100644 --- a/LibGit2Sharp/Core/GitIndexReucEntry.cs +++ b/LibGit2Sharp/Core/GitIndexReucEntry.cs @@ -4,14 +4,14 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitIndexReucEntry + internal unsafe struct git_index_reuc_entry { public uint AncestorMode; public uint OurMode; public uint TheirMode; - public GitOid AncestorId; - public GitOid OurId; - public GitOid TheirId; - public IntPtr Path; + public git_oid AncestorId; + public git_oid OurId; + public git_oid TheirId; + public char* Path; } } diff --git a/LibGit2Sharp/Core/GitIndexTime.cs b/LibGit2Sharp/Core/GitIndexTime.cs index fb47f2944..9f16c5121 100644 --- a/LibGit2Sharp/Core/GitIndexTime.cs +++ b/LibGit2Sharp/Core/GitIndexTime.cs @@ -5,7 +5,7 @@ namespace LibGit2Sharp.Core [StructLayout(LayoutKind.Sequential)] internal class GitIndexTime { - public long seconds; + public int seconds; public uint nanoseconds; } } diff --git a/LibGit2Sharp/Core/GitMergeOpts.cs b/LibGit2Sharp/Core/GitMergeOpts.cs index a2ebe979d..48675a2d0 100644 --- a/LibGit2Sharp/Core/GitMergeOpts.cs +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -8,7 +8,7 @@ internal struct GitMergeOpts { public uint Version; - public GitMergeTreeFlags MergeTreeFlags; + public GitMergeFlag MergeTreeFlags; /// /// Similarity to consider a file renamed. @@ -27,10 +27,29 @@ internal struct GitMergeOpts /// public IntPtr SimilarityMetric; + /// + /// Maximum number of times to merge common ancestors to build a + /// virtual merge base when faced with criss-cross merges. When this + /// limit is reached, the next ancestor will simply be used instead of + /// attempting to merge it. The default is unlimited. + /// + public uint RecursionLimit; + + /// + /// Default merge driver to be used when both sides of a merge have + /// changed. The default is the `text` driver. + /// + public string DefaultDriver; + /// /// Flags for automerging content. /// public MergeFileFavor MergeFileFavorFlags; + + /// + /// File merging flags. + /// + public GitMergeFileFlag FileFlags; } /// @@ -63,11 +82,11 @@ internal enum GitMergeAnalysis /// GIT_MERGE_ANALYSIS_FASTFORWARD = (1 << 2), - /** - * The HEAD of the current repository is "unborn" and does not point to - * a valid commit. No merge can be performed, but the caller may wish - * to simply set HEAD to the target commit(s). - */ + /// + /// The HEAD of the current repository is "unborn" and does not point to + /// a valid commit. No merge can be performed, but the caller may wish + /// to simply set HEAD to the target commit(s). + /// GIT_MERGE_ANALYSIS_UNBORN = (1 << 3), } @@ -93,16 +112,87 @@ internal enum GitMergePreference } [Flags] - internal enum GitMergeTreeFlags + internal enum GitMergeFlag { /// /// No options. /// - GIT_MERGE_TREE_NORMAL = 0, + GIT_MERGE_NORMAL = 0, + + /// + /// Detect renames that occur between the common ancestor and the "ours" + /// side or the common ancestor and the "theirs" side. This will enable + /// the ability to merge between a modified and renamed file. + /// + GIT_MERGE_FIND_RENAMES = (1 << 0), + + /// + /// If a conflict occurs, exit immediately instead of attempting to + /// continue resolving conflicts. The merge operation will fail with + /// GIT_EMERGECONFLICT and no index will be returned. + /// + GIT_MERGE_FAIL_ON_CONFLICT = (1 << 1), + + /// + /// Do not write the REUC extension on the generated index + /// + GIT_MERGE_SKIP_REUC = (1 << 2), + + /// + /// If the commits being merged have multiple merge bases, do not build + /// a recursive merge base (by merging the multiple merge bases), + /// instead simply use the first base. This flag provides a similar + /// merge base to `git-merge-resolve`. + /// + GIT_MERGE_NO_RECURSIVE = (1 << 3), + } + + [Flags] + internal enum GitMergeFileFlag + { + /// + /// Defaults + /// + GIT_MERGE_FILE_DEFAULT = 0, + + /// + /// Create standard conflicted merge files + /// + GIT_MERGE_FILE_STYLE_MERGE = (1 << 0), + + /// + /// Create diff3-style files + /// + GIT_MERGE_FILE_STYLE_DIFF3 = (1 << 1), + + /// + /// Condense non-alphanumeric regions for simplified diff file + /// + GIT_MERGE_FILE_SIMPLIFY_ALNUM = (1 << 2), + + /// + /// Ignore all whitespace + /// + GIT_MERGE_FILE_IGNORE_WHITESPACE = (1 << 3), + + /// + /// Ignore changes in amount of whitespace + /// + GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE = (1 << 4), + + /// + /// Ignore whitespace at end of line + /// + GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = (1 << 5), + + /// + /// Use the "patience diff" algorithm + /// + GIT_MERGE_FILE_DIFF_PATIENCE = (1 << 6), /// - /// GIT_MERGE_TREE_FIND_RENAMES in libgit2 + /// Take extra time to find minimal diff /// - GIT_MERGE_TREE_FIND_RENAMES = (1 << 0), + GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), } } diff --git a/LibGit2Sharp/Core/GitObjectLazyGroup.cs b/LibGit2Sharp/Core/GitObjectLazyGroup.cs index 7d4429166..f00900837 100644 --- a/LibGit2Sharp/Core/GitObjectLazyGroup.cs +++ b/LibGit2Sharp/Core/GitObjectLazyGroup.cs @@ -1,9 +1,10 @@ using System; +using System.Diagnostics.CodeAnalysis; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp.Core { - internal class GitObjectLazyGroup : LazyGroup + internal class GitObjectLazyGroup : LazyGroup { private readonly ObjectId id; @@ -13,7 +14,7 @@ public GitObjectLazyGroup(Repository repo, ObjectId id) this.id = id; } - protected override void EvaluateInternal(Action evaluator) + protected override void EvaluateInternal(Action evaluator) { using (var osw = new ObjectSafeWrapper(id, repo.Handle)) { @@ -21,12 +22,19 @@ protected override void EvaluateInternal(Action evaluator) } } - public static ILazy Singleton(Repository repo, ObjectId id, Func resultSelector) +#if NET + public static ILazy Singleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(Repository repo, ObjectId id, Func resultSelector, bool throwIfMissing = false) +#else + public static ILazy Singleton(Repository repo, ObjectId id, Func resultSelector, bool throwIfMissing = false) +#endif + { return Singleton(() => { - using (var osw = new ObjectSafeWrapper(id, repo.Handle)) + using (var osw = new ObjectSafeWrapper(id, repo.Handle, throwIfMissing: throwIfMissing)) + { return resultSelector(osw.ObjectPtr); + } }); } } diff --git a/LibGit2Sharp/Core/GitObjectType.cs b/LibGit2Sharp/Core/GitObjectType.cs index a38523f2c..50d8412c2 100644 --- a/LibGit2Sharp/Core/GitObjectType.cs +++ b/LibGit2Sharp/Core/GitObjectType.cs @@ -75,8 +75,9 @@ public static TreeEntryTargetType ToTreeEntryTargetType(this GitObjectType type) return TreeEntryTargetType.Blob; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a TreeEntryTargetType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a TreeEntryTargetType.", + type)); } } @@ -97,8 +98,9 @@ public static ObjectType ToObjectType(this GitObjectType type) return ObjectType.Tag; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a ObjectType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a ObjectType.", + type)); } } } diff --git a/LibGit2Sharp/Core/GitOdbBackend.cs b/LibGit2Sharp/Core/GitOdbBackend.cs index a7b0acf41..c102c94eb 100644 --- a/LibGit2Sharp/Core/GitOdbBackend.cs +++ b/LibGit2Sharp/Core/GitOdbBackend.cs @@ -8,7 +8,7 @@ internal struct GitOdbBackend { static GitOdbBackend() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitOdbBackend), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public uint Version; @@ -33,7 +33,9 @@ static GitOdbBackend() public exists_prefix_callback ExistsPrefix; public IntPtr Refresh; public foreach_callback Foreach; - public IntPtr Writepack; + public IntPtr WritePack; + public IntPtr WriteMidx; + public IntPtr Freshen; public free_callback Free; /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ @@ -54,6 +56,7 @@ static GitOdbBackend() /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The OID which the backend is being asked to look up. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( out IntPtr buffer_p, out UIntPtr len_p, @@ -76,6 +79,7 @@ public delegate int read_callback( /// [in] The short-form OID which the backend is being asked to look up. /// [in] The length of the short-form OID (short_oid). /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_prefix_callback( out GitOid out_oid, out IntPtr buffer_p, @@ -94,6 +98,7 @@ public delegate int read_prefix_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The OID which the backend is being asked to look up. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_header_callback( out UIntPtr len_p, out GitObjectType type_p, @@ -110,6 +115,7 @@ public delegate int read_header_callback( /// [in] The length of the buffer pointed to by data. /// [in] The type of the object. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr backend, ref GitOid oid, @@ -127,10 +133,11 @@ public delegate int write_callback( /// [in] The length of the object's contents. /// [in] The type of the object being written. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int writestream_callback( out IntPtr stream_out, IntPtr backend, - UIntPtr length, + long length, GitObjectType type); /// @@ -141,6 +148,7 @@ public delegate int writestream_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The object ID that the caller is requesting. /// 0 if successful; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int readstream_callback( out IntPtr stream_out, IntPtr backend, @@ -153,6 +161,7 @@ public delegate int readstream_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The object ID that the caller is requesting. /// True if the object exists; false otherwise + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate bool exists_callback( IntPtr backend, ref GitOid oid); @@ -168,6 +177,7 @@ public delegate bool exists_callback( /// [in] The short-form OID which the backend is being asked to look up. /// [in] The length of the short-form OID (short_oid). /// 1 if the object exists, 0 if the object doesn't; an error code otherwise. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int exists_prefix_callback( ref GitOid found_oid, IntPtr backend, @@ -181,6 +191,7 @@ public delegate int exists_prefix_callback( /// [in] A pointer to the backend which is being asked to perform the task. /// [in] The callback function to invoke. /// [in] An arbitrary parameter to pass through to the callback + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int foreach_callback( IntPtr backend, foreach_callback_callback cb, @@ -190,6 +201,7 @@ public delegate int foreach_callback( /// The owner of this backend is finished with it. The backend is asked to clean up and shut down. /// /// [in] A pointer to the backend which is being freed. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback( IntPtr backend); @@ -199,6 +211,7 @@ public delegate void free_callback( /// The oid of each object in the backing store. /// The arbitrary parameter given to foreach_callback. /// A non-negative result indicates the enumeration should continue. Otherwise, the enumeration should stop. + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int foreach_callback_callback( IntPtr oid, IntPtr data); diff --git a/LibGit2Sharp/Core/GitOdbBackendStream.cs b/LibGit2Sharp/Core/GitOdbBackendStream.cs index 82f6ce89c..14b126c7a 100644 --- a/LibGit2Sharp/Core/GitOdbBackendStream.cs +++ b/LibGit2Sharp/Core/GitOdbBackendStream.cs @@ -15,15 +15,15 @@ internal class GitOdbBackendStream { static GitOdbBackendStream() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitOdbBackendStream), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public IntPtr Backend; public GitOdbBackendStreamMode Mode; public IntPtr HashCtx; - public UIntPtr DeclaredSize; - public UIntPtr ReceivedBytes; + public long DeclaredSize; + public long ReceivedBytes; public read_callback Read; public write_callback Write; @@ -38,21 +38,22 @@ static GitOdbBackendStream() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( IntPtr stream, IntPtr buffer, UIntPtr len); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr stream, IntPtr buffer, UIntPtr len); - public delegate int finalize_write_callback( - IntPtr stream, - ref GitOid oid); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int finalize_write_callback(IntPtr stream, ref GitOid oid); - public delegate void free_callback( - IntPtr stream); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback(IntPtr stream); } } diff --git a/LibGit2Sharp/Core/GitOid.cs b/LibGit2Sharp/Core/GitOid.cs index 9c5930908..f466621b1 100644 --- a/LibGit2Sharp/Core/GitOid.cs +++ b/LibGit2Sharp/Core/GitOid.cs @@ -2,6 +2,12 @@ namespace LibGit2Sharp.Core { + internal struct git_oid + { + public const int Size = 20; + public unsafe fixed byte Id[20]; + } + /// /// Represents a unique id in git which is the sha1 hash of this id's content. /// @@ -27,5 +33,13 @@ public static implicit operator ObjectId(GitOid? oid) { return oid == null ? null : new ObjectId(oid.Value); } + + /// + /// Static convenience property to return an id (all zeros). + /// + public static GitOid Empty + { + get { return new GitOid(); } + } } } diff --git a/LibGit2Sharp/Core/GitProxyOptions.cs b/LibGit2Sharp/Core/GitProxyOptions.cs new file mode 100644 index 000000000..85d4057ab --- /dev/null +++ b/LibGit2Sharp/Core/GitProxyOptions.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal enum GitProxyType + { + None, + Auto, + Specified + } + + [StructLayout(LayoutKind.Sequential)] + internal struct GitProxyOptions + { + public uint Version; + public GitProxyType Type; + public IntPtr Url; + public NativeMethods.git_cred_acquire_cb Credentials; + public NativeMethods.git_transport_certificate_check_cb CertificateCheck; + public IntPtr Payload; + } +} diff --git a/LibGit2Sharp/Core/GitProxyOptionsWrapper.cs b/LibGit2Sharp/Core/GitProxyOptionsWrapper.cs new file mode 100644 index 000000000..053213e96 --- /dev/null +++ b/LibGit2Sharp/Core/GitProxyOptionsWrapper.cs @@ -0,0 +1,32 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal class GitProxyOptionsWrapper : IDisposable + { + public GitProxyOptionsWrapper() : this(new GitProxyOptions()) { } + + public GitProxyOptionsWrapper(GitProxyOptions fetchOptions) + { + Options = fetchOptions; + } + + public GitProxyOptions Options { get; private set; } + + private bool disposedValue = false; + + protected virtual void Dispose(bool disposing) + { + if (disposedValue) + return; + + EncodingMarshaler.Cleanup(Options.Url); + disposedValue = true; + } + + public void Dispose() + { + Dispose(true); + } + } +} diff --git a/LibGit2Sharp/Core/GitPushOptions.cs b/LibGit2Sharp/Core/GitPushOptions.cs index bf6058d67..ac9a99e1e 100644 --- a/LibGit2Sharp/Core/GitPushOptions.cs +++ b/LibGit2Sharp/Core/GitPushOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { @@ -8,5 +7,10 @@ internal class GitPushOptions { public int Version = 1; public int PackbuilderDegreeOfParallelism; + public GitRemoteCallbacks RemoteCallbacks; + public GitProxyOptions ProxyOptions; + public RemoteRedirectMode FollowRedirects = RemoteRedirectMode.Initial; + public GitStrArrayManaged CustomHeaders; + public GitStrArrayManaged remote_push_options; } } diff --git a/LibGit2Sharp/Core/GitPushOptionsWrapper.cs b/LibGit2Sharp/Core/GitPushOptionsWrapper.cs new file mode 100644 index 000000000..3ccffcf06 --- /dev/null +++ b/LibGit2Sharp/Core/GitPushOptionsWrapper.cs @@ -0,0 +1,37 @@ +using System; + +namespace LibGit2Sharp.Core +{ + /// + /// Git push options wrapper. Disposable wrapper for . + /// + internal class GitPushOptionsWrapper : IDisposable + { + public GitPushOptionsWrapper() : this(new GitPushOptions()) { } + + public GitPushOptionsWrapper(GitPushOptions pushOptions) + { + this.Options = pushOptions; + } + + public GitPushOptions Options { get; private set; } + + #region IDisposable + private bool disposedValue = false; // To detect redundant calls + protected virtual void Dispose(bool disposing) + { + if (disposedValue) + return; + + this.Options.CustomHeaders.Dispose(); + EncodingMarshaler.Cleanup(Options.ProxyOptions.Url); + disposedValue = true; + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/LibGit2Sharp/Core/GitPushUpdate.cs b/LibGit2Sharp/Core/GitPushUpdate.cs new file mode 100644 index 000000000..ef0ba827a --- /dev/null +++ b/LibGit2Sharp/Core/GitPushUpdate.cs @@ -0,0 +1,14 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_push_update + { + public char* src_refname; + public char* dst_refname; + public git_oid src; + public git_oid dst; + } +} diff --git a/LibGit2Sharp/Core/GitRebaseOperation.cs b/LibGit2Sharp/Core/GitRebaseOperation.cs new file mode 100644 index 000000000..4db167a49 --- /dev/null +++ b/LibGit2Sharp/Core/GitRebaseOperation.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_rebase_operation + { + internal RebaseStepOperation type; + internal git_oid id; + internal char* exec; + } +} diff --git a/LibGit2Sharp/Core/GitRebaseOptions.cs b/LibGit2Sharp/Core/GitRebaseOptions.cs new file mode 100644 index 000000000..981bfe919 --- /dev/null +++ b/LibGit2Sharp/Core/GitRebaseOptions.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitRebaseOptions + { + public uint version = 1; + + public int quiet; + + public int inmemory; + + public IntPtr rewrite_notes_ref; + + public GitMergeOpts merge_options = new GitMergeOpts { Version = 1 }; + + public GitCheckoutOpts checkout_options = new GitCheckoutOpts { version = 1 }; + + private IntPtr padding; // TODO: add git_commit_create_cb + + public NativeMethods.commit_signing_callback signing_callback; + } +} diff --git a/LibGit2Sharp/Core/GitRemoteCallbacks.cs b/LibGit2Sharp/Core/GitRemoteCallbacks.cs index 3aec0bfdc..4900ad562 100644 --- a/LibGit2Sharp/Core/GitRemoteCallbacks.cs +++ b/LibGit2Sharp/Core/GitRemoteCallbacks.cs @@ -17,12 +17,26 @@ internal struct GitRemoteCallbacks internal NativeMethods.git_cred_acquire_cb acquire_credentials; - internal IntPtr certificate_check; + internal NativeMethods.git_transport_certificate_check_cb certificate_check; internal NativeMethods.git_transfer_progress_callback download_progress; internal NativeMethods.remote_update_tips_callback update_tips; + internal NativeMethods.git_packbuilder_progress pack_progress; + + internal NativeMethods.git_push_transfer_progress push_transfer_progress; + + internal NativeMethods.push_update_reference_callback push_update_reference; + + internal NativeMethods.push_negotiation_callback push_negotiation; + + internal IntPtr transport; + + private IntPtr padding; // TODO: add git_remote_ready_cb + internal IntPtr payload; + + internal NativeMethods.url_resolve_callback resolve_url; } } diff --git a/LibGit2Sharp/Core/GitRemoteHead.cs b/LibGit2Sharp/Core/GitRemoteHead.cs index 02c2def8b..cbaf3c818 100644 --- a/LibGit2Sharp/Core/GitRemoteHead.cs +++ b/LibGit2Sharp/Core/GitRemoteHead.cs @@ -4,12 +4,12 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal struct GitRemoteHead + internal unsafe struct git_remote_head { - public bool Local; - public GitOid Oid; - public GitOid Loid; - public IntPtr NamePtr; - public IntPtr SymRefTargetPtr; + public int Local; + public git_oid Oid; + public git_oid Loid; + public char* Name; + public char* SymrefTarget; } } diff --git a/LibGit2Sharp/Core/GitRepositoryInitOptions.cs b/LibGit2Sharp/Core/GitRepositoryInitOptions.cs index c42bc83f4..f639a0d8d 100644 --- a/LibGit2Sharp/Core/GitRepositoryInitOptions.cs +++ b/LibGit2Sharp/Core/GitRepositoryInitOptions.cs @@ -19,10 +19,10 @@ internal class GitRepositoryInitOptions : IDisposable public static GitRepositoryInitOptions BuildFrom(FilePath workdirPath, bool isBare) { var opts = new GitRepositoryInitOptions - { - Flags = GitRepositoryInitFlags.GIT_REPOSITORY_INIT_MKPATH, - Mode = 0 /* GIT_REPOSITORY_INIT_SHARED_UMASK */ - }; + { + Flags = GitRepositoryInitFlags.GIT_REPOSITORY_INIT_MKPATH, + Mode = 0 /* GIT_REPOSITORY_INIT_SHARED_UMASK */ + }; if (workdirPath != null) { diff --git a/LibGit2Sharp/Core/GitRevertOpts.cs b/LibGit2Sharp/Core/GitRevertOpts.cs index 7976243c2..3d6583a81 100644 --- a/LibGit2Sharp/Core/GitRevertOpts.cs +++ b/LibGit2Sharp/Core/GitRevertOpts.cs @@ -12,6 +12,6 @@ internal class GitRevertOpts public GitMergeOpts MergeOpts = new GitMergeOpts { Version = 1 }; - public GitCheckoutOpts CheckoutOpts = new GitCheckoutOpts {version = 1}; + public GitCheckoutOpts CheckoutOpts = new GitCheckoutOpts { version = 1 }; } } diff --git a/LibGit2Sharp/Core/GitSignature.cs b/LibGit2Sharp/Core/GitSignature.cs index 3261d4c57..5641af368 100644 --- a/LibGit2Sharp/Core/GitSignature.cs +++ b/LibGit2Sharp/Core/GitSignature.cs @@ -4,10 +4,10 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitSignature + internal unsafe struct git_signature { - public IntPtr Name; - public IntPtr Email; - public GitTime When; + public char* name; + public char* email; + public git_time when; } } diff --git a/LibGit2Sharp/Core/GitSmartSubtransport.cs b/LibGit2Sharp/Core/GitSmartSubtransport.cs index d9b3c7545..d4ce6d728 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransport.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransport.cs @@ -8,7 +8,7 @@ internal class GitSmartSubtransport { static GitSmartSubtransport() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransport), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public action_callback Action; @@ -23,14 +23,17 @@ static GitSmartSubtransport() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int action_callback( out IntPtr stream, IntPtr subtransport, IntPtr url, GitSmartSubtransportAction action); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int close_callback(IntPtr subtransport); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void free_callback(IntPtr subtransport); } } diff --git a/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs index 724c6c414..c8ae4fde7 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs @@ -8,9 +8,12 @@ internal class GitSmartSubtransportRegistration { public IntPtr SubtransportCallback; public uint Rpc; + public uint Param; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int create_callback( out IntPtr subtransport, - IntPtr transport); + IntPtr owner, + IntPtr param); } } diff --git a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs index f73218c09..ae371b980 100644 --- a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs +++ b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs @@ -8,7 +8,7 @@ internal class GitSmartSubtransportStream { static GitSmartSubtransportStream() { - GCHandleOffset = Marshal.OffsetOf(typeof(GitSmartSubtransportStream), "GCHandle").ToInt32(); + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); } public IntPtr SmartTransport; @@ -25,18 +25,20 @@ static GitSmartSubtransportStream() public static int GCHandleOffset; + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int read_callback( IntPtr stream, IntPtr buffer, UIntPtr buf_size, out UIntPtr bytes_read); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate int write_callback( IntPtr stream, IntPtr buffer, UIntPtr len); - public delegate void free_callback( - IntPtr stream); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_callback(IntPtr stream); } } diff --git a/LibGit2Sharp/Core/GitStashApplyOpts.cs b/LibGit2Sharp/Core/GitStashApplyOpts.cs new file mode 100644 index 000000000..739c40b0c --- /dev/null +++ b/LibGit2Sharp/Core/GitStashApplyOpts.cs @@ -0,0 +1,20 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int stash_apply_progress_cb(StashApplyProgress progress, IntPtr payload); + + [StructLayout(LayoutKind.Sequential)] + internal class GitStashApplyOpts + { + public uint Version = 1; + + public StashApplyModifiers Flags; + public GitCheckoutOpts CheckoutOptions; + + public stash_apply_progress_cb ApplyProgressCallback; + public IntPtr ProgressPayload; + } +} diff --git a/LibGit2Sharp/Core/GitStatusEntry.cs b/LibGit2Sharp/Core/GitStatusEntry.cs index 2e13a186a..73e6547a8 100644 --- a/LibGit2Sharp/Core/GitStatusEntry.cs +++ b/LibGit2Sharp/Core/GitStatusEntry.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp.Core { @@ -10,21 +7,21 @@ namespace LibGit2Sharp.Core /// A status entry from libgit2. /// [StructLayout(LayoutKind.Sequential)] - internal class GitStatusEntry + internal unsafe struct git_status_entry { /// /// Calculated status of a filepath in the working directory considering the current and the . /// - public FileStatus Status; + public FileStatus status; /// /// The difference between the and . /// - public IntPtr HeadToIndexPtr; + public git_diff_delta* head_to_index; /// /// The difference between the and the working directory. /// - public IntPtr IndexToWorkDirPtr; + public git_diff_delta* index_to_workdir; } } diff --git a/LibGit2Sharp/Core/GitStatusOptions.cs b/LibGit2Sharp/Core/GitStatusOptions.cs index bc0956e8f..d577cefe6 100644 --- a/LibGit2Sharp/Core/GitStatusOptions.cs +++ b/LibGit2Sharp/Core/GitStatusOptions.cs @@ -1,8 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp.Core { @@ -14,7 +11,9 @@ internal class GitStatusOptions : IDisposable public GitStatusShow Show; public GitStatusOptionFlags Flags; - GitStrArrayManaged PathSpec; + public GitStrArrayManaged PathSpec; + + public IntPtr Baseline = IntPtr.Zero; public void Dispose() { diff --git a/LibGit2Sharp/Core/GitStrArray.cs b/LibGit2Sharp/Core/GitStrArray.cs index a94187e1b..d2efca8b7 100644 --- a/LibGit2Sharp/Core/GitStrArray.cs +++ b/LibGit2Sharp/Core/GitStrArray.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; -using System.Text; namespace LibGit2Sharp.Core { diff --git a/LibGit2Sharp/Core/GitStrArrayNative.cs b/LibGit2Sharp/Core/GitStrArrayNative.cs index 2db550c0f..01cd18e6e 100644 --- a/LibGit2Sharp/Core/GitStrArrayNative.cs +++ b/LibGit2Sharp/Core/GitStrArrayNative.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Runtime.InteropServices; namespace LibGit2Sharp.Core @@ -16,11 +15,11 @@ internal struct GitStrArrayNative : IDisposable /// /// Enumerates each string from the array using the UTF-8 marshaler. /// - public String[] ReadStrings() + public string[] ReadStrings() { var count = checked((int)Array.Count.ToUInt32()); - String[] toReturn = new String[count]; + string[] toReturn = new string[count]; for (int i = 0; i < count; i++) { diff --git a/LibGit2Sharp/Core/GitSubmoduleIgnore.cs b/LibGit2Sharp/Core/GitSubmoduleIgnore.cs new file mode 100644 index 000000000..5550864a6 --- /dev/null +++ b/LibGit2Sharp/Core/GitSubmoduleIgnore.cs @@ -0,0 +1,11 @@ +namespace LibGit2Sharp.Core +{ + internal enum GitSubmoduleIgnore + { + Unspecified = -1, + None = 1, + Untracked = 2, + Dirty = 3, + All = 4, + } +} diff --git a/LibGit2Sharp/Core/GitSubmoduleOptions.cs b/LibGit2Sharp/Core/GitSubmoduleOptions.cs new file mode 100644 index 000000000..09a8e8265 --- /dev/null +++ b/LibGit2Sharp/Core/GitSubmoduleOptions.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitSubmoduleUpdateOptions + { + public uint Version; + + public GitCheckoutOpts CheckoutOptions; + + public GitFetchOptions FetchOptions; + + public int AllowFetch; + } +} diff --git a/LibGit2Sharp/Core/GitTime.cs b/LibGit2Sharp/Core/GitTime.cs index 304500d0c..e8051048e 100644 --- a/LibGit2Sharp/Core/GitTime.cs +++ b/LibGit2Sharp/Core/GitTime.cs @@ -3,9 +3,9 @@ namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] - internal class GitTime + internal unsafe struct git_time { - public long Time; - public int Offset; + public long time; + public int offset; } } diff --git a/LibGit2Sharp/Core/GitWorktree.cs b/LibGit2Sharp/Core/GitWorktree.cs new file mode 100644 index 000000000..2e17bc20d --- /dev/null +++ b/LibGit2Sharp/Core/GitWorktree.cs @@ -0,0 +1,55 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /** + * Flags which can be passed to git_worktree_prune to alter its + * behavior. + */ + [Flags] + internal enum GitWorktreePruneOptionFlags : uint + { + /// + /// Prune working tree even if working tree is valid + /// + GIT_WORKTREE_PRUNE_VALID = (1u << 0), + + /// + /// Prune working tree even if it is locked + /// + GIT_WORKTREE_PRUNE_LOCKED = (1u << 1), + + /// + /// Prune checked out working tree + /// + GIT_WORKTREE_PRUNE_WORKING_TREE = (1u << 2) + } + + + [StructLayout(LayoutKind.Sequential)] + internal class git_worktree_add_options + { + public uint version = 1; + + public int locked; + + public int checkout_existing; + + public IntPtr @ref = IntPtr.Zero; + + public GitCheckoutOpts checkoutOpts = new GitCheckoutOpts + { + version = 1, + checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_SAFE + }; + } + + [StructLayout(LayoutKind.Sequential)] + internal class git_worktree_prune_options + { + public uint version = 1; + + public GitWorktreePruneOptionFlags flags; + } +} diff --git a/LibGit2Sharp/Core/GitWriteStream.cs b/LibGit2Sharp/Core/GitWriteStream.cs new file mode 100644 index 000000000..891a765fb --- /dev/null +++ b/LibGit2Sharp/Core/GitWriteStream.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitWriteStream + { + [MarshalAs(UnmanagedType.FunctionPtr)] + public write_fn write; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public close_fn close; + + [MarshalAs(UnmanagedType.FunctionPtr)] + public free_fn free; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int write_fn(IntPtr stream, IntPtr buffer, UIntPtr len); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int close_fn(IntPtr stream); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate void free_fn(IntPtr stream); + } +} diff --git a/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs b/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs deleted file mode 100644 index e8218e3da..000000000 --- a/LibGit2Sharp/Core/Handles/BlameSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class BlameSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_blame_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs b/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs deleted file mode 100644 index 6764bee66..000000000 --- a/LibGit2Sharp/Core/Handles/BranchIteratorSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class BranchIteratorSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_branch_iterator_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs deleted file mode 100644 index 0d2cb6ab6..000000000 --- a/LibGit2Sharp/Core/Handles/ConfigurationIteratorSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConfigurationIteratorSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_config_iterator_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs deleted file mode 100644 index 01b2a4c9d..000000000 --- a/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConfigurationSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_config_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs b/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs deleted file mode 100644 index fe117cba1..000000000 --- a/LibGit2Sharp/Core/Handles/DiffSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class DiffSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_diff_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs b/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs deleted file mode 100644 index 5d9d4a2b6..000000000 --- a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitConfigEntryHandle : NotOwnedSafeHandleBase - { - public GitConfigEntry MarshalAsGitConfigEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs deleted file mode 100644 index 8ae8c3c8c..000000000 --- a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitErrorSafeHandle : NotOwnedSafeHandleBase - { - public GitError MarshalAsGitError() - { - // Required on Mono < 3.0.8 - // https://bugzilla.xamarin.com/show_bug.cgi?id=11417 - // https://github.com/mono/mono/commit/9cdddca7ec283f3b9181f3f69c1acecc0d9cc289 - if (handle == IntPtr.Zero) - { - return null; - } - - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs b/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs deleted file mode 100644 index f49e30e54..000000000 --- a/LibGit2Sharp/Core/Handles/GitMergeHeadHandle.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitMergeHeadHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_merge_head_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs deleted file mode 100644 index 46de2dfe7..000000000 --- a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitObjectSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_object_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs b/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs deleted file mode 100644 index a795472f6..000000000 --- a/LibGit2Sharp/Core/Handles/GitRefSpecHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitRefSpecHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs deleted file mode 100644 index 87e0f1dc4..000000000 --- a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexEntry MarshalAsGitIndexEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs deleted file mode 100644 index 549d41e6a..000000000 --- a/LibGit2Sharp/Core/Handles/IndexNameEntrySafeHandle.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexNameEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexNameEntry MarshalAsGitIndexNameEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs deleted file mode 100644 index ec006b39a..000000000 --- a/LibGit2Sharp/Core/Handles/IndexReucEntrySafeHandle.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexReucEntrySafeHandle : NotOwnedSafeHandleBase - { - public GitIndexReucEntry MarshalAsGitIndexReucEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs deleted file mode 100644 index d757efb68..000000000 --- a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_index_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/Libgit2Object.cs b/LibGit2Sharp/Core/Handles/Libgit2Object.cs new file mode 100644 index 000000000..a96d99e10 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Libgit2Object.cs @@ -0,0 +1,141 @@ +// This activates a lightweight mode which will help put under the light +// incorrectly released handles by outputting a warning message in the console. +// +// This should be activated when tests are being run on the CI server. +// +// Uncomment the line below or add a conditional symbol to activate this mode + +//#define LEAKS_IDENTIFYING + +// This activates a more thorough mode which will show the stack trace of the +// allocation code path for each handle that has been improperly released. +// +// This should be manually activated when some warnings have been raised as +// a result of LEAKS_IDENTIFYING mode activation. +// +// Uncomment the line below or add a conditional symbol to activate this mode + +//#define LEAKS_TRACKING + +using System; +using Microsoft.Win32.SafeHandles; + +#if LEAKS_IDENTIFYING +namespace LibGit2Sharp.Core +{ + using System.Collections.Generic; + using System.Linq; + + /// + /// Holds leaked handle type names reported by + /// + public static class LeaksContainer + { + private static readonly HashSet _typeNames = new HashSet(); + private static readonly object _lockpad = new object(); + + /// + /// Report a new leaked handle type name + /// + /// Short name of the leaked handle type. + public static void Add(string typeName) + { + lock (_lockpad) + { + _typeNames.Add(typeName); + } + } + + /// + /// Removes all previously reported leaks. + /// + public static void Clear() + { + lock (_lockpad) + { + _typeNames.Clear(); + } + } + + /// + /// Returns all reported leaked handle type names. + /// + public static IEnumerable TypeNames + { + get + { + string[] result = null; + lock (_lockpad) + { + result = _typeNames.ToArray(); + } + return result; + } + } + } +} +#endif + +namespace LibGit2Sharp.Core.Handles +{ +#if LEAKS_TRACKING + using System.Diagnostics; + using System.Globalization; +#endif + + internal unsafe abstract class Libgit2Object : SafeHandleZeroOrMinusOneIsInvalid + { +#if LEAKS_TRACKING + private readonly string trace; + private readonly Guid id; +#endif + + internal unsafe Libgit2Object(void* ptr, bool owned) + : this(new IntPtr(ptr), owned) + { + } + + internal unsafe Libgit2Object(IntPtr ptr, bool owned) + : base(owned) + { + SetHandle(ptr); + +#if LEAKS_TRACKING + id = Guid.NewGuid(); + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Allocating {0} handle ({1})", GetType().Name, id)); + + trace = new StackTrace(2, true).ToString(); +#endif + } + + internal IntPtr AsIntPtr() => DangerousGetHandle(); + + protected override void Dispose(bool disposing) + { +#if LEAKS_IDENTIFYING + bool leaked = !disposing && DangerousGetHandle() != IntPtr.Zero; + + if (leaked) + { + LeaksContainer.Add(GetType().Name); + } +#endif + + base.Dispose(disposing); + +#if LEAKS_TRACKING + if (!leaked) + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Disposing {0} handle ({1})", GetType().Name, id)); + } + else + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Unexpected finalization of {0} handle ({1})", GetType().Name, id)); + Trace.WriteLine(trace); + Trace.WriteLine(""); + } +#endif + } + } +} + diff --git a/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs b/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs deleted file mode 100644 index d9c05abf4..000000000 --- a/LibGit2Sharp/Core/Handles/NotOwnedSafeHandleBase.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal abstract class NotOwnedSafeHandleBase : SafeHandle - { - protected NotOwnedSafeHandleBase() - : base(IntPtr.Zero, false) - { - } - - public override bool IsInvalid - { - get { return IsZero; } - } - - public bool IsZero - { - get { return (handle == IntPtr.Zero); } - } - - protected override bool ReleaseHandle() - { - // Does nothing as the pointer is owned by libgit2 - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs b/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs deleted file mode 100644 index a62c5105a..000000000 --- a/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class NoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_note_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs b/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs deleted file mode 100644 index 5c4521193..000000000 --- a/LibGit2Sharp/Core/Handles/NullGitObjectSafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullGitObjectSafeHandle : GitObjectSafeHandle - { - public NullGitObjectSafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs deleted file mode 100644 index dcd4988fb..000000000 --- a/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullIndexSafeHandle : IndexSafeHandle - { - public NullIndexSafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs b/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs deleted file mode 100644 index cf57b96fc..000000000 --- a/LibGit2Sharp/Core/Handles/NullRepositorySafeHandle.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class NullRepositorySafeHandle : SafeHandleBase - { - public NullRepositorySafeHandle() - { - handle = IntPtr.Zero; - } - - protected override bool ReleaseHandleImpl() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs b/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs deleted file mode 100644 index e8cfb2b0a..000000000 --- a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ObjectDatabaseSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_odb_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/Objects.cs b/LibGit2Sharp/Core/Handles/Objects.cs new file mode 100644 index 000000000..ddca49bee --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Objects.cs @@ -0,0 +1,632 @@ + +using System; + +namespace LibGit2Sharp.Core.Handles +{ + + internal unsafe class TreeEntryHandle : Libgit2Object + { + internal TreeEntryHandle(git_tree_entry* ptr, bool owned) + : base(ptr, owned) + { + } + + internal TreeEntryHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_tree_entry_free((git_tree_entry*)AsIntPtr()); + + return true; + } + + public static implicit operator git_tree_entry*(TreeEntryHandle handle) + { + return (git_tree_entry*)handle.AsIntPtr(); + } + } + + internal unsafe class ReferenceHandle : Libgit2Object + { + internal ReferenceHandle(git_reference* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ReferenceHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_reference_free((git_reference*)AsIntPtr()); + + return true; + } + + public static implicit operator git_reference*(ReferenceHandle handle) + { + return (git_reference*)handle.AsIntPtr(); + } + } + + internal unsafe class RepositoryHandle : Libgit2Object + { + internal RepositoryHandle(git_repository* ptr, bool owned) + : base(ptr, owned) + { + } + + internal RepositoryHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_repository_free((git_repository*)AsIntPtr()); + + return true; + } + + public static implicit operator git_repository*(RepositoryHandle handle) + { + return (git_repository*)handle.AsIntPtr(); + } + } + + internal unsafe class SignatureHandle : Libgit2Object + { + internal SignatureHandle(git_signature* ptr, bool owned) + : base(ptr, owned) + { + } + + internal SignatureHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_signature_free((git_signature*)AsIntPtr()); + + return true; + } + + public static implicit operator git_signature*(SignatureHandle handle) + { + return (git_signature*)handle.AsIntPtr(); + } + } + + internal unsafe class StatusListHandle : Libgit2Object + { + internal StatusListHandle(git_status_list* ptr, bool owned) + : base(ptr, owned) + { + } + + internal StatusListHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_status_list_free((git_status_list*)AsIntPtr()); + + return true; + } + + public static implicit operator git_status_list*(StatusListHandle handle) + { + return (git_status_list*)handle.AsIntPtr(); + } + } + + internal unsafe class BlameHandle : Libgit2Object + { + internal BlameHandle(git_blame* ptr, bool owned) + : base(ptr, owned) + { + } + + internal BlameHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_blame_free((git_blame*)AsIntPtr()); + + return true; + } + + public static implicit operator git_blame*(BlameHandle handle) + { + return (git_blame*)handle.AsIntPtr(); + } + } + + internal unsafe class DiffHandle : Libgit2Object + { + internal DiffHandle(git_diff* ptr, bool owned) + : base(ptr, owned) + { + } + + internal DiffHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_diff_free((git_diff*)AsIntPtr()); + + return true; + } + + public static implicit operator git_diff*(DiffHandle handle) + { + return (git_diff*)handle.AsIntPtr(); + } + } + + internal unsafe class PatchHandle : Libgit2Object + { + internal PatchHandle(git_patch* ptr, bool owned) + : base(ptr, owned) + { + } + + internal PatchHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_patch_free((git_patch*)AsIntPtr()); + + return true; + } + + public static implicit operator git_patch*(PatchHandle handle) + { + return (git_patch*)handle.AsIntPtr(); + } + } + + internal unsafe class ConfigurationHandle : Libgit2Object + { + internal ConfigurationHandle(git_config* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ConfigurationHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_config_free((git_config*)AsIntPtr()); + + return true; + } + + public static implicit operator git_config*(ConfigurationHandle handle) + { + return (git_config*)handle.AsIntPtr(); + } + } + + internal unsafe class ConflictIteratorHandle : Libgit2Object + { + internal ConflictIteratorHandle(git_index_conflict_iterator* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ConflictIteratorHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_index_conflict_iterator_free((git_index_conflict_iterator*)AsIntPtr()); + + return true; + } + + public static implicit operator git_index_conflict_iterator*(ConflictIteratorHandle handle) + { + return (git_index_conflict_iterator*)handle.AsIntPtr(); + } + } + + internal unsafe class IndexHandle : Libgit2Object + { + internal IndexHandle(git_index* ptr, bool owned) + : base(ptr, owned) + { + } + + internal IndexHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_index_free((git_index*)AsIntPtr()); + + return true; + } + + public static implicit operator git_index*(IndexHandle handle) + { + return (git_index*)handle.AsIntPtr(); + } + } + + internal unsafe class ReflogHandle : Libgit2Object + { + internal ReflogHandle(git_reflog* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ReflogHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_reflog_free((git_reflog*)AsIntPtr()); + + return true; + } + + public static implicit operator git_reflog*(ReflogHandle handle) + { + return (git_reflog*)handle.AsIntPtr(); + } + } + + internal unsafe class TreeBuilderHandle : Libgit2Object + { + internal TreeBuilderHandle(git_treebuilder* ptr, bool owned) + : base(ptr, owned) + { + } + + internal TreeBuilderHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_treebuilder_free((git_treebuilder*)AsIntPtr()); + + return true; + } + + public static implicit operator git_treebuilder*(TreeBuilderHandle handle) + { + return (git_treebuilder*)handle.AsIntPtr(); + } + } + + internal unsafe class PackBuilderHandle : Libgit2Object + { + internal PackBuilderHandle(git_packbuilder* ptr, bool owned) + : base(ptr, owned) + { + } + + internal PackBuilderHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_packbuilder_free((git_packbuilder*)AsIntPtr()); + + return true; + } + + public static implicit operator git_packbuilder*(PackBuilderHandle handle) + { + return (git_packbuilder*)handle.AsIntPtr(); + } + } + + internal unsafe class NoteHandle : Libgit2Object + { + internal NoteHandle(git_note* ptr, bool owned) + : base(ptr, owned) + { + } + + internal NoteHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_note_free((git_note*)AsIntPtr()); + + return true; + } + + public static implicit operator git_note*(NoteHandle handle) + { + return (git_note*)handle.AsIntPtr(); + } + } + + internal unsafe class DescribeResultHandle : Libgit2Object + { + internal DescribeResultHandle(git_describe_result* ptr, bool owned) + : base(ptr, owned) + { + } + + internal DescribeResultHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_describe_result_free((git_describe_result*)AsIntPtr()); + + return true; + } + + public static implicit operator git_describe_result*(DescribeResultHandle handle) + { + return (git_describe_result*)handle.AsIntPtr(); + } + } + + internal unsafe class SubmoduleHandle : Libgit2Object + { + internal SubmoduleHandle(git_submodule* ptr, bool owned) + : base(ptr, owned) + { + } + + internal SubmoduleHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_submodule_free((git_submodule*)AsIntPtr()); + + return true; + } + + public static implicit operator git_submodule*(SubmoduleHandle handle) + { + return (git_submodule*)handle.AsIntPtr(); + } + } + + internal unsafe class AnnotatedCommitHandle : Libgit2Object + { + internal AnnotatedCommitHandle(git_annotated_commit* ptr, bool owned) + : base(ptr, owned) + { + } + + internal AnnotatedCommitHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_annotated_commit_free((git_annotated_commit*)AsIntPtr()); + + return true; + } + + public static implicit operator git_annotated_commit*(AnnotatedCommitHandle handle) + { + return (git_annotated_commit*)handle.AsIntPtr(); + } + } + + internal unsafe class ObjectDatabaseHandle : Libgit2Object + { + internal ObjectDatabaseHandle(git_odb* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ObjectDatabaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_odb_free((git_odb*)AsIntPtr()); + + return true; + } + + public static implicit operator git_odb*(ObjectDatabaseHandle handle) + { + return (git_odb*)handle.AsIntPtr(); + } + } + + internal unsafe class RevWalkerHandle : Libgit2Object + { + internal RevWalkerHandle(git_revwalk* ptr, bool owned) + : base(ptr, owned) + { + } + + internal RevWalkerHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_revwalk_free((git_revwalk*)AsIntPtr()); + + return true; + } + + public static implicit operator git_revwalk*(RevWalkerHandle handle) + { + return (git_revwalk*)handle.AsIntPtr(); + } + } + + internal unsafe class RemoteHandle : Libgit2Object + { + internal RemoteHandle(git_remote* ptr, bool owned) + : base(ptr, owned) + { + } + + internal RemoteHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_remote_free((git_remote*)AsIntPtr()); + + return true; + } + + public static implicit operator git_remote*(RemoteHandle handle) + { + return (git_remote*)handle.AsIntPtr(); + } + } + + internal unsafe class ObjectHandle : Libgit2Object + { + internal ObjectHandle(git_object* ptr, bool owned) + : base(ptr, owned) + { + } + + internal ObjectHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_object_free((git_object*)AsIntPtr()); + + return true; + } + + public static implicit operator git_object*(ObjectHandle handle) + { + return (git_object*)handle.AsIntPtr(); + } + } + + internal unsafe class RebaseHandle : Libgit2Object + { + internal RebaseHandle(git_rebase* ptr, bool owned) + : base(ptr, owned) + { + } + + internal RebaseHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_rebase_free((git_rebase*)AsIntPtr()); + + return true; + } + + public static implicit operator git_rebase*(RebaseHandle handle) + { + return (git_rebase*)handle.AsIntPtr(); + } + } + + internal unsafe class OdbStreamHandle : Libgit2Object + { + internal OdbStreamHandle(git_odb_stream* ptr, bool owned) + : base(ptr, owned) + { + } + + internal OdbStreamHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_odb_stream_free((git_odb_stream*)AsIntPtr()); + + return true; + } + + public static implicit operator git_odb_stream*(OdbStreamHandle handle) + { + return (git_odb_stream*)handle.AsIntPtr(); + } + } + + internal unsafe class WorktreeHandle : Libgit2Object + { + internal WorktreeHandle(git_worktree* ptr, bool owned) + : base(ptr, owned) + { + } + + internal WorktreeHandle(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.git_worktree_free((git_worktree*)AsIntPtr()); + + return true; + } + + public static implicit operator git_worktree*(WorktreeHandle handle) + { + return (git_worktree*)handle.AsIntPtr(); + } + } + +} diff --git a/LibGit2Sharp/Core/Handles/Objects.tt b/LibGit2Sharp/Core/Handles/Objects.tt new file mode 100644 index 000000000..e522bd859 --- /dev/null +++ b/LibGit2Sharp/Core/Handles/Objects.tt @@ -0,0 +1,101 @@ +<#@ template language="C#" #> +<#@ output extention=".cs" #> +<#@ assembly name="System.Core" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Text" #> +<#@ import namespace="System.Collections.Generic" #> + +using System; + +namespace LibGit2Sharp.Core.Handles +{ + +<# +var cNames = new[] { + "git_tree_entry", + "git_reference", + "git_repository", + "git_signature", + "git_status_list", + "git_blame", + "git_diff", + "git_patch", + "git_config", + "git_index_conflict_iterator", + "git_index", + "git_reflog", + "git_treebuilder", + "git_packbuilder", + "git_note", + "git_describe_result", + "git_submodule", + "git_annotated_commit", + "git_odb", + "git_revwalk", + "git_remote", + "git_object", + "git_rebase", + "git_odb_stream", + "git_worktree", +}; + +var csNames = new[] { + "TreeEntryHandle", + "ReferenceHandle", + "RepositoryHandle", + "SignatureHandle", + "StatusListHandle", + "BlameHandle", + "DiffHandle", + "PatchHandle", + "ConfigurationHandle", + "ConflictIteratorHandle", + "IndexHandle", + "ReflogHandle", + "TreeBuilderHandle", + "PackBuilderHandle", + "NoteHandle", + "DescribeResultHandle", + "SubmoduleHandle", + "AnnotatedCommitHandle", + "ObjectDatabaseHandle", + "RevWalkerHandle", + "RemoteHandle", + "ObjectHandle", + "RebaseHandle", + "OdbStreamHandle", + "WorktreeHandle" +}; + +for (var i = 0; i < cNames.Length; i++) +{ +#> + internal unsafe class <#= csNames[i] #> : Libgit2Object + { + internal <#= csNames[i] #>(<#= cNames[i] #>* ptr, bool owned) + : base(ptr, owned) + { + } + + internal <#= csNames[i] #>(IntPtr ptr, bool owned) + : base(ptr, owned) + { + } + + protected override bool ReleaseHandle() + { + NativeMethods.<#= cNames[i] #>_free((<#= cNames[i] #>*)AsIntPtr()); + + return true; + } + + public static implicit operator <#= cNames[i] #>*(<#= csNames[i] #> handle) + { + return (<#= cNames[i] #>*)handle.AsIntPtr(); + } + } + +<# +} +#> +} diff --git a/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs b/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs deleted file mode 100644 index 10dc69db8..000000000 --- a/LibGit2Sharp/Core/Handles/OdbStreamSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class OdbStreamSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_odb_stream_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs b/LibGit2Sharp/Core/Handles/OidSafeHandle.cs deleted file mode 100644 index f6e00ec31..000000000 --- a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class OidSafeHandle : NotOwnedSafeHandleBase - { - private GitOid? MarshalAsGitOid() - { - return IsInvalid ? null : (GitOid?)MarshalAsGitOid(handle); - } - - private static GitOid MarshalAsGitOid(System.IntPtr data) - { - var gitOid = new GitOid { Id = new byte[GitOid.Size] }; - Marshal.Copy(data, gitOid.Id, 0, GitOid.Size); - return gitOid; - } - - public ObjectId MarshalAsObjectId() - { - return MarshalAsGitOid(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs b/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs deleted file mode 100644 index 97c0dc9bb..000000000 --- a/LibGit2Sharp/Core/Handles/PatchSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class PatchSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_patch_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/PushSafeHandle.cs b/LibGit2Sharp/Core/Handles/PushSafeHandle.cs deleted file mode 100644 index ba958d956..000000000 --- a/LibGit2Sharp/Core/Handles/PushSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class PushSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_push_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs b/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs deleted file mode 100644 index 9ac7d1f7e..000000000 --- a/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReferenceSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_reference_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs deleted file mode 100644 index 1739ccac3..000000000 --- a/LibGit2Sharp/Core/Handles/ReflogEntrySafeHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReflogEntrySafeHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs b/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs deleted file mode 100644 index a75deabea..000000000 --- a/LibGit2Sharp/Core/Handles/ReflogSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReflogSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_reflog_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs b/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs deleted file mode 100644 index d032e34f5..000000000 --- a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RemoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_remote_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs b/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs deleted file mode 100644 index 889a3022c..000000000 --- a/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RepositorySafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_repository_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs b/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs deleted file mode 100644 index 457bd027c..000000000 --- a/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RevWalkerSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_revwalk_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SafeHandleBase.cs b/LibGit2Sharp/Core/Handles/SafeHandleBase.cs deleted file mode 100644 index ae386bd39..000000000 --- a/LibGit2Sharp/Core/Handles/SafeHandleBase.cs +++ /dev/null @@ -1,116 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; -using System.Threading; - -namespace LibGit2Sharp.Core.Handles -{ - internal abstract class SafeHandleBase : SafeHandle - { -#if LEAKS - private readonly string trace; -#endif - - /// - /// This is set to non-zero when has - /// been called for this handle, but - /// has not yet been called. - /// - private int registered; - - protected SafeHandleBase() - : base(IntPtr.Zero, true) - { - NativeMethods.AddHandle(); - registered = 1; -#if LEAKS - trace = new StackTrace(2, true).ToString(); -#endif - } - -#if DEBUG - protected override void Dispose(bool disposing) - { - if (!disposing && !IsInvalid) - { - Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "A {0} handle wrapper has not been properly disposed.", GetType().Name)); -#if LEAKS - Trace.WriteLine(trace); -#endif - Trace.WriteLine(""); - } - - base.Dispose(disposing); - } -#endif - - // Prevent the debugger from evaluating this property because it has side effects - [DebuggerBrowsable(DebuggerBrowsableState.Never)] - public override sealed bool IsInvalid - { - get - { - bool invalid = IsInvalidImpl(); - if (invalid && Interlocked.CompareExchange(ref registered, 0, 1) == 1) - { - /* Unregister the handle because we know ReleaseHandle won't be called - * to do it for us. - */ - NativeMethods.RemoveHandle(); - } - - return invalid; - } - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - protected virtual bool IsInvalidImpl() - { - return handle == IntPtr.Zero; - } - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - protected abstract bool ReleaseHandleImpl(); - - protected override sealed bool ReleaseHandle() - { - bool result; - - try - { - result = ReleaseHandleImpl(); - } - finally - { - if (Interlocked.CompareExchange(ref registered, 0, 1) == 1) - { - // if the handle is still registered at this point, we definitely - // want to unregister it - NativeMethods.RemoveHandle(); - } - else - { - /* For this to be called, the following sequence of events must occur: - * - * 1. The handle is created - * 2. The IsInvalid property is evaluated, and the result is false - * 3. The IsInvalid property is evaluated by the runtime to determine if - * finalization is necessary, and the result is now true - * - * This can only happen if the value of `handle` is manipulated in an unexpected - * way (through the Reflection API or by a specially-crafted derived type that - * does not currently exist). The only safe course of action at this point in - * the shutdown process is returning false, which will trigger the - * releaseHandleFailed MDA but have no other impact on the CLR state. - * http://msdn.microsoft.com/en-us/library/85eak4a0.aspx - */ - result = false; - } - } - - return result; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs b/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs deleted file mode 100644 index 51f745ae9..000000000 --- a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class SignatureSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_signature_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs deleted file mode 100644 index 45b6eacd4..000000000 --- a/LibGit2Sharp/Core/Handles/StatusEntrySafeHandle.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal class StatusEntrySafeHandle : NotOwnedSafeHandleBase - { - public StatusEntrySafeHandle() - : base() - { - } - - public StatusEntrySafeHandle(IntPtr handle) - : base() - { - this.SetHandle(handle); - } - - public GitStatusEntry MarshalAsGitStatusEntry() - { - return handle.MarshalAs(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs b/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs deleted file mode 100644 index 74316a3fa..000000000 --- a/LibGit2Sharp/Core/Handles/StatusListSafeHandle.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp.Core.Handles -{ - internal class StatusListSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_status_list_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs b/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs deleted file mode 100644 index 3e84b29b1..000000000 --- a/LibGit2Sharp/Core/Handles/SubmoduleSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class SubmoduleSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_submodule_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs b/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs deleted file mode 100644 index 1fd17a292..000000000 --- a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeBuilderSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_treebuilder_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs deleted file mode 100644 index f621cb2ff..000000000 --- a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeEntrySafeHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs b/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs deleted file mode 100644 index f2d63e13f..000000000 --- a/LibGit2Sharp/Core/Handles/TreeEntrySafeHandle_Owned.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeEntrySafeHandle_Owned : SafeHandleBase - { - protected override bool ReleaseHandleImpl() - { - Proxy.git_tree_entry_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/HistoryRewriter.cs b/LibGit2Sharp/Core/HistoryRewriter.cs index 33c35c6fd..094d5ca1c 100644 --- a/LibGit2Sharp/Core/HistoryRewriter.cs +++ b/LibGit2Sharp/Core/HistoryRewriter.cs @@ -45,21 +45,25 @@ public void Execute() var filter = new CommitFilter { - Since = refsToRewrite, + IncludeReachableFrom = refsToRewrite, SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological }; var commits = repo.Commits.QueryBy(filter); foreach (var commit in commits) { - RewriteCommit(commit); + RewriteCommit(commit, options); } // Ordering matters. In the case of `A -> B -> commit`, we need to make sure B is rewritten // before A. foreach (var reference in refsToRewrite.OrderBy(ReferenceDepth)) { - // TODO: Check how rewriting of notes actually behaves + // TODO: Rewrite refs/notes/* properly + if (reference.CanonicalName.StartsWith("refs/notes/")) + { + continue; + } RewriteReference(reference); } @@ -106,24 +110,33 @@ private Reference RewriteReference(Reference reference) var sref = reference as SymbolicReference; if (sref != null) { - return RewriteReference( - sref, old => old.Target, RewriteReference, - (refs, old, target, sig, logMessage) => refs.UpdateTarget(old, target, sig, logMessage)); + return RewriteReference(sref, + old => old.Target, + RewriteReference, + (refs, old, target, logMessage) => refs.UpdateTarget(old, + target, + logMessage)); } var dref = reference as DirectReference; if (dref != null) { - return RewriteReference( - dref, old => old.Target, RewriteTarget, - (refs, old, target, sig, logMessage) => refs.UpdateTarget(old, target.Id, sig, logMessage)); + return RewriteReference(dref, + old => old.Target, + RewriteTarget, + (refs, old, target, logMessage) => refs.UpdateTarget(old, + target.Id, + logMessage)); } return reference; } private delegate Reference ReferenceUpdater( - ReferenceCollection refs, TRef origRef, TTarget origTarget, Signature signature, string logMessage) + ReferenceCollection refs, + TRef origRef, + TTarget origTarget, + string logMessage) where TRef : Reference where TTarget : class; @@ -135,14 +148,14 @@ private Reference RewriteReference( where TTarget : class { var oldRefTarget = selectTarget(oldRef); - var signature = repo.Config.BuildSignature(DateTimeOffset.Now); string newRefName = oldRef.CanonicalName; - if (oldRef.IsTag() && options.TagNameRewriter != null) + if (oldRef.IsTag && options.TagNameRewriter != null) { newRefName = Reference.TagPrefix + options.TagNameRewriter(oldRef.CanonicalName.Substring(Reference.TagPrefix.Length), - false, oldRef.TargetIdentifier); + false, + oldRef.TargetIdentifier); } var newTarget = rewriteTarget(oldRefTarget); @@ -157,36 +170,36 @@ private Reference RewriteReference( if (repo.Refs.Resolve(backupName) != null) { - throw new InvalidOperationException( - String.Format( - CultureInfo.InvariantCulture, "Can't back up reference '{0}' - '{1}' already exists", - oldRef.CanonicalName, backupName)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Can't back up reference '{0}' - '{1}' already exists", + oldRef.CanonicalName, + backupName)); } - repo.Refs.Add(backupName, oldRef.TargetIdentifier, signature, "filter-branch: backup"); + repo.Refs.Add(backupName, oldRef.TargetIdentifier, "filter-branch: backup"); rollbackActions.Enqueue(() => repo.Refs.Remove(backupName)); if (newTarget == null) { repo.Refs.Remove(oldRef); - rollbackActions.Enqueue(() => repo.Refs.Add(oldRef.CanonicalName, oldRef, signature, "filter-branch: abort", true)); + rollbackActions.Enqueue(() => repo.Refs.Add(oldRef.CanonicalName, oldRef, "filter-branch: abort", true)); return refMap[oldRef] = null; } - Reference newRef = updateTarget(repo.Refs, oldRef, newTarget, signature, "filter-branch: rewrite"); - rollbackActions.Enqueue(() => updateTarget(repo.Refs, oldRef, oldRefTarget, signature, "filter-branch: abort")); + Reference newRef = updateTarget(repo.Refs, oldRef, newTarget, "filter-branch: rewrite"); + rollbackActions.Enqueue(() => updateTarget(repo.Refs, oldRef, oldRefTarget, "filter-branch: abort")); if (newRef.CanonicalName == newRefName) { return refMap[oldRef] = newRef; } - var movedRef = repo.Refs.Rename(newRef, newRefName); - rollbackActions.Enqueue(() => repo.Refs.Rename(newRef, oldRef.CanonicalName)); + var movedRef = repo.Refs.Rename(newRef, newRefName, false); + rollbackActions.Enqueue(() => repo.Refs.Rename(newRef, oldRef.CanonicalName, false)); return refMap[oldRef] = movedRef; } - private void RewriteCommit(Commit commit) + private void RewriteCommit(Commit commit, RewriteHistoryOptions options) { var newHeader = CommitRewriteInfo.From(commit); var newTree = commit.Tree; @@ -218,8 +231,7 @@ private void RewriteCommit(Commit commit) // Create the new commit var mappedNewParents = newParents - .Select(oldParent => - objectMap.ContainsKey(oldParent) + .Select(oldParent => objectMap.ContainsKey(oldParent) ? objectMap[oldParent] as Commit : oldParent) .Where(newParent => newParent != null) @@ -236,7 +248,7 @@ private void RewriteCommit(Commit commit) newHeader.Message, newTree, mappedNewParents, - true); + options.PrettifyMessages); // Record the rewrite objectMap[commit] = newCommit; @@ -290,8 +302,10 @@ private GitObject RewriteTarget(GitObject oldTarget) newName = options.TagNameRewriter(annotation.Name, true, annotation.Target.Sha); } - var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, newTarget, annotation.Tagger, - annotation.Message); + var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, + newTarget, + annotation.Tagger, + annotation.Message); objectMap[annotation] = newAnnotation; return newAnnotation; } @@ -300,8 +314,8 @@ private int ReferenceDepth(Reference reference) { var dref = reference as DirectReference; return dref == null - ? 1 + ReferenceDepth(((SymbolicReference)reference).Target) - : 1; + ? 1 + ReferenceDepth(((SymbolicReference)reference).Target) + : 1; } } } diff --git a/LibGit2Sharp/Core/IntPtrExtensions.cs b/LibGit2Sharp/Core/IntPtrExtensions.cs deleted file mode 100644 index 314a16834..000000000 --- a/LibGit2Sharp/Core/IntPtrExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core -{ - internal static class IntPtrExtensions - { - public static T MarshalAs(this IntPtr ptr, bool throwWhenNull = true) - { - if (!throwWhenNull && ptr == IntPtr.Zero) - { - return default(T); - } - return (T)Marshal.PtrToStructure(ptr, typeof(T)); - } - } -} diff --git a/LibGit2Sharp/Core/LambdaEqualityHelper.cs b/LibGit2Sharp/Core/LambdaEqualityHelper.cs index 120e705de..80e826c02 100644 --- a/LibGit2Sharp/Core/LambdaEqualityHelper.cs +++ b/LibGit2Sharp/Core/LambdaEqualityHelper.cs @@ -48,7 +48,7 @@ public int GetHashCode(T instance) foreach (Func accessor in equalityContributorAccessors) { object item = accessor(instance); - hashCode = (hashCode*397) ^ (item != null ? item.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (item != null ? item.GetHashCode() : 0); } } diff --git a/LibGit2Sharp/Core/LazyGroup.cs b/LibGit2Sharp/Core/LazyGroup.cs index 3c82fa3ad..bcd160290 100644 --- a/LibGit2Sharp/Core/LazyGroup.cs +++ b/LibGit2Sharp/Core/LazyGroup.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; namespace LibGit2Sharp.Core { @@ -24,18 +25,19 @@ public ILazy AddLazy(Func func) public void Evaluate() { - if (evaluated) - return; - lock (@lock) { if (evaluated) + { return; + } EvaluateInternal(input => { foreach (var e in evaluators) + { e.Evaluate(input); + } }); evaluated = true; } @@ -43,7 +45,11 @@ public void Evaluate() protected abstract void EvaluateInternal(Action evaluator); +#if NET + protected static ILazy Singleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TResult>(Func resultSelector) +#else protected static ILazy Singleton(Func resultSelector) +#endif { return new LazyWrapper(resultSelector); } @@ -89,12 +95,15 @@ void IEvaluator.Evaluate(TInput input) } } +#if NET + protected class LazyWrapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TType> : Lazy, ILazy +#else protected class LazyWrapper : Lazy, ILazy +#endif { public LazyWrapper(Func evaluator) : base(evaluator) - { - } + { } } } } diff --git a/LibGit2Sharp/Core/NativeDllName.cs b/LibGit2Sharp/Core/NativeDllName.cs deleted file mode 100644 index 16e5f7800..000000000 --- a/LibGit2Sharp/Core/NativeDllName.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace LibGit2Sharp.Core -{ - internal static class NativeDllName - { - public const string Name = "git2-e0383fa"; - } -} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 113e91f15..cbb850b16 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1,409 +1,587 @@ using System; -using System.Globalization; using System.IO; +#if NET using System.Reflection; +#endif using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; -using System.Threading; using LibGit2Sharp.Core.Handles; -// ReSharper disable InconsistentNaming +// Restrict the set of directories where the native library is loaded from to safe directories. +[assembly: DefaultDllImportSearchPaths(DllImportSearchPath.AssemblyDirectory | DllImportSearchPath.ApplicationDirectory | DllImportSearchPath.SafeDirectories)] + namespace LibGit2Sharp.Core { internal static class NativeMethods { public const uint GIT_PATH_MAX = 4096; private const string libgit2 = NativeDllName.Name; - private static readonly LibraryLifetimeObject lifetimeObject; - private static int handlesCount; - - /// - /// Internal hack to ensure that the call to git_threads_shutdown is called after all handle finalizers - /// have run to completion ensuring that no dangling git-related finalizer runs after git_threads_shutdown. - /// There should never be more than one instance of this object per AppDomain. - /// - private sealed class LibraryLifetimeObject : CriticalFinalizerObject + + // An object tied to the lifecycle of the NativeMethods static class. + // This will handle initialization and shutdown of the underlying + // native library. + private static NativeShutdownObject shutdownObject; + + static NativeMethods() { - // Ensure mono can JIT the .cctor and adjust the PATH before trying to load the native library. - // See https://github.com/libgit2/libgit2sharp/pull/190 - [MethodImpl(MethodImplOptions.NoInlining)] - public LibraryLifetimeObject() + if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore()) { - Ensure.ZeroResult(git_threads_init()); - AddHandle(); + // Use NativeLibrary when available. + if (!TryUseNativeLibrary()) + { + // NativeLibrary is not available, fall back. + + // Use GlobalSettings.NativeLibraryPath when set. + // Try to load the .dll from the path explicitly. + // If this call succeeds further DllImports will find the library loaded and not attempt to load it again. + // If it fails the next DllImport will load the library from safe directories. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); + + if (nativeLibraryPath != null) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + + { + LoadWindowsLibrary(nativeLibraryPath); + } + else + { + LoadUnixLibrary(nativeLibraryPath, RTLD_NOW); + } + } + } } - ~LibraryLifetimeObject() - { - RemoveHandle(); - } + InitializeNativeLibrary(); } - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal static void AddHandle() + private static string GetGlobalSettingsNativeLibraryPath() { - Interlocked.Increment(ref handlesCount); - } + string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] - internal static void RemoveHandle() - { - int count = Interlocked.Decrement(ref handlesCount); - if (count == 0) + if (nativeLibraryDir == null) { - git_threads_shutdown(); + return null; } + + return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); } - static NativeMethods() +#if NETFRAMEWORK + private static bool TryUseNativeLibrary() => false; +#else + private static bool TryUseNativeLibrary() + { + NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDll); + + return true; + } + + private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) { - if (!IsRunningOnLinux()) + IntPtr handle = IntPtr.Zero; + + if (libraryName == libgit2) { - string originalAssemblypath = new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase).LocalPath; + // Use GlobalSettings.NativeLibraryPath when set. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); - string currentArchSubPath = "NativeBinaries/" + ProcessorArchitecture; + if (nativeLibraryPath != null && NativeLibrary.TryLoad(nativeLibraryPath, out handle)) + { + return handle; + } - string path = Path.Combine(Path.GetDirectoryName(originalAssemblypath), currentArchSubPath); + // Use Default DllImport resolution. + if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out handle)) + { + return handle; + } - const string pathEnvVariable = "PATH"; - Environment.SetEnvironmentVariable(pathEnvVariable, - String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable))); + // We carry a number of .so files for Linux which are linked against various + // libc/OpenSSL libraries. Try them out. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // The libraries are located at 'runtimes//native/lib{libraryName}.so' + // The ends with the processor architecture. e.g. fedora-x64. + string assemblyDirectory = Path.GetDirectoryName(AppContext.BaseDirectory); + string processorArchitecture = RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + string runtimesDirectory = Path.Combine(assemblyDirectory, "runtimes"); + + if (Directory.Exists(runtimesDirectory)) + { + foreach (var runtimeFolder in Directory.GetDirectories(runtimesDirectory, $"*-{processorArchitecture}")) + { + string libPath = Path.Combine(runtimeFolder, "native", $"lib{libraryName}.so"); + + if (NativeLibrary.TryLoad(libPath, out handle)) + { + return handle; + } + } + } + } } - // See LibraryLifetimeObject description. - lifetimeObject = new LibraryLifetimeObject(); + return handle; } +#endif + + public const int RTLD_NOW = 0x002; + + [DllImport("libdl", EntryPoint = "dlopen")] + private static extern IntPtr LoadUnixLibrary(string path, int flags); - public static string ProcessorArchitecture + [DllImport("kernel32", EntryPoint = "LoadLibrary")] + private static extern IntPtr LoadWindowsLibrary(string path); + + // Avoid inlining this method because otherwise mono's JITter may try + // to load the library _before_ we've configured the path. + [MethodImpl(MethodImplOptions.NoInlining)] + private static void InitializeNativeLibrary() { - get + int initCounter; + try { - if (Environment.Is64BitProcess) - { - return "amd64"; - } + } + finally // avoid thread aborts + { + // Initialization can be called multiple times as long as there is a corresponding shutdown to each initialization. + initCounter = git_libgit2_init(); + shutdownObject = new NativeShutdownObject(); + } - return "x86"; + // Configure the OpenSSL locking on the first initialization of the library in the current process. + if (initCounter == 1) + { + git_openssl_set_locking(); } } - // Should match LibGit2Sharp.Tests.TestHelpers.BaseFixture.IsRunningOnLinux() - private static bool IsRunningOnLinux() + // Shutdown the native library in a finalizer. + private sealed class NativeShutdownObject : CriticalFinalizerObject { - // see http://mono-project.com/FAQ%3a_Technical#Mono_Platforms - var p = (int)Environment.OSVersion.Platform; - return (p == 4) || (p == 6) || (p == 128); + ~NativeShutdownObject() + { + git_libgit2_shutdown(); + } } - [DllImport(libgit2)] - internal static extern GitErrorSafeHandle giterr_last(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitError* git_error_last(); - [DllImport(libgit2)] - internal static extern void giterr_set_str( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_error_set_str( GitErrorCategory error_class, - string errorString); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string errorString); - [DllImport(libgit2)] - internal static extern void giterr_set_oom(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_error_set_oom(); - [DllImport(libgit2)] - internal static extern UInt32 git_blame_get_hunk_count(BlameSafeHandle blame); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_blame_get_hunk_count(git_blame* blame); - [DllImport(libgit2)] - internal static extern IntPtr git_blame_get_hunk_byindex( - BlameSafeHandle blame, UInt32 index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_blame_hunk* git_blame_get_hunk_byindex( + git_blame* blame, uint index); - [DllImport(libgit2)] - internal static extern int git_blame_file( - out BlameSafeHandle blame, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, - GitBlameOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blame_file( + out git_blame* blame, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path, + git_blame_options options); - [DllImport(libgit2)] - internal static extern void git_blame_free(IntPtr blame); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_blame_free(git_blame* blame); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromdisk( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_from_disk( ref GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromworkdir( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_from_workdir( ref GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath relative_path); - internal delegate int source_callback( - IntPtr content, - int max_length, - IntPtr data); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_from_stream( + out IntPtr stream, + git_repository* repositoryPtr, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hintpath); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromchunks( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_blob_create_from_stream_commit( ref GitOid oid, - RepositorySafeHandle repositoryPtr, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath hintpath, - source_callback fileCallback, - IntPtr data); + IntPtr stream); - [DllImport(libgit2)] - internal static extern int git_blob_filtered_content( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_filtered_content( GitBuf buf, - GitObjectSafeHandle blob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath as_path, + git_object* blob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string as_path, [MarshalAs(UnmanagedType.Bool)] bool check_for_binary_data); - [DllImport(libgit2)] - internal static extern IntPtr git_blob_rawcontent(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe IntPtr git_blob_rawcontent(git_object* blob); - [DllImport(libgit2)] - internal static extern Int64 git_blob_rawsize(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe long git_blob_rawsize(git_object* blob); - [DllImport(libgit2)] - internal static extern int git_branch_create( - out ReferenceSafeHandle ref_out, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_create_from_annotated( + out git_reference* ref_out, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, - GitObjectSafeHandle target, // TODO: GitCommitSafeHandle? - [MarshalAs(UnmanagedType.Bool)] bool force, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + git_annotated_commit* target, + [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_branch_delete( - ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_delete( + git_reference* reference); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int branch_foreach_callback( IntPtr branch_name, GitBranchType branch_type, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_branch_iterator_free( IntPtr iterator); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_branch_iterator_new( - out BranchIteratorSafeHandle iter_out, - RepositorySafeHandle repo, + out IntPtr iter_out, + IntPtr repo, GitBranchType branch_type); - [DllImport(libgit2)] - internal static extern int git_branch_move( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_move( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_branch_name, - [MarshalAs(UnmanagedType.Bool)] bool force, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_branch_next( - out ReferenceSafeHandle ref_out, + out IntPtr ref_out, out GitBranchType type_out, - BranchIteratorSafeHandle iter); + IntPtr iter); - [DllImport(libgit2)] - internal static extern int git_branch_remote_name( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_remote_name( GitBuf buf, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string canonical_branch_name); - [DllImport(libgit2)] - internal static extern int git_remote_rename( + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int commit_signing_callback( + IntPtr signature, + IntPtr signature_field, + IntPtr commit_content, + IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_init( + out git_rebase* rebase, + git_repository* repo, + git_annotated_commit* branch, + git_annotated_commit* upstream, + git_annotated_commit* onto, + GitRebaseOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_open( + out git_rebase* rebase, + git_repository* repo, + GitRebaseOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_rebase_operation_entrycount( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_rebase_operation_current( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_rebase_operation* git_rebase_operation_byindex( + git_rebase* rebase, + UIntPtr index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_next( + out git_rebase_operation* operation, + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_commit( + ref GitOid id, + git_rebase* rebase, + git_signature* author, + git_signature* committer, + IntPtr message_encoding, + IntPtr message); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_abort( + git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_rebase_finish( + git_rebase* repo, + git_signature* signature); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_rebase_free(git_rebase* rebase); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_rename( ref GitStrArray problems, - RemoteSafeHandle remote, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string old_name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_name); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_remote_rename_problem_cb( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] string problematic_refspec, IntPtr payload); - - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.Bool)] - internal static extern bool git_remote_supported_url( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - - [DllImport(libgit2)] - internal static extern int git_branch_upstream_name( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_upstream_name( GitBuf buf, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string referenceName); - [DllImport(libgit2)] - internal static extern void git_buf_free(GitBuf buf); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_buf_dispose(GitBuf buf); - [DllImport(libgit2)] - internal static extern int git_checkout_tree( - RepositorySafeHandle repo, - GitObjectSafeHandle treeish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_checkout_tree( + git_repository* repo, + git_object* treeish, ref GitCheckoutOpts opts); - [DllImport(libgit2)] - internal static extern int git_checkout_index( - RepositorySafeHandle repo, - GitObjectSafeHandle treeish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_checkout_index( + git_repository* repo, + git_object* treeish, ref GitCheckoutOpts opts); - [DllImport(libgit2)] - internal static extern int git_clone( - out RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_clone( + out git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string origin_url, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath workdir_path, ref GitCloneOptions opts); - [DllImport(libgit2)] - internal static extern IntPtr git_commit_author(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_commit_author(git_object* commit); - [DllImport(libgit2)] - internal static extern IntPtr git_commit_committer(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_commit_committer(git_object* commit); - [DllImport(libgit2)] - internal static extern int git_commit_create_from_ids( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_from_ids( out GitOid id, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string updateRef, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_signature* author, + git_signature* committer, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string encoding, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, ref GitOid tree, UIntPtr parentCount, - [MarshalAs(UnmanagedType.LPArray)] [In] IntPtr[] parents); + [MarshalAs(UnmanagedType.LPArray)][In] IntPtr[] parents); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_buffer( + GitBuf res, + git_repository* repo, + git_signature* author, + git_signature* committer, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string encoding, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, + git_object* tree, + UIntPtr parent_count, + IntPtr* parents /* git_commit** originally */); - [DllImport(libgit2)] - [return : MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_message(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_with_signature( + out GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string commit_content, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string signature_field); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_summary(GitObjectSafeHandle commit); + internal static extern unsafe string git_commit_message(git_object* commit); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_commit_message_encoding(GitObjectSafeHandle commit); + internal static extern unsafe string git_commit_summary(git_object* commit); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_commit_message_encoding(git_object* commit); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_commit_parent_id(git_object* commit, uint n); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_parent_id(GitObjectSafeHandle commit, uint n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_commit_parentcount(git_object* commit); - [DllImport(libgit2)] - internal static extern uint git_commit_parentcount(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_commit_tree_id(git_object* commit); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_tree_id(GitObjectSafeHandle commit); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_extract_signature( + GitBuf signature, + GitBuf signed_data, + git_repository* repo, + ref GitOid commit_id, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string field); - [DllImport(libgit2)] - internal static extern int git_config_delete_entry(ConfigurationSafeHandle cfg, string name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_delete_entry( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_lock(out IntPtr txn, git_config* config); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_delete_multivar( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_multivar( + git_config* cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_global(GitBuf global_config_path); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_system(GitBuf system_config_path); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_find_xdg(GitBuf xdg_config_path); - [DllImport(libgit2)] - internal static extern void git_config_free(IntPtr cfg); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_find_programdata(GitBuf programdata_config_path); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_config_free(git_config* cfg); - [DllImport(libgit2)] - internal static extern int git_config_get_entry( - out GitConfigEntryHandle entry, - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_config_entry_free(GitConfigEntry* entry); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_get_entry( + out GitConfigEntry* entry, + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern int git_config_add_file_ondisk( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_add_file_ondisk( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, uint level, + git_repository* repo, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_config_new(out ConfigurationSafeHandle cfg); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_new(out git_config* cfg); - [DllImport(libgit2)] - internal static extern int git_config_open_level( - out ConfigurationSafeHandle cfg, - ConfigurationSafeHandle parent, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_open_level( + out git_config* cfg, + git_config* parent, uint level); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_bool( [MarshalAs(UnmanagedType.Bool)] out bool value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_int32( [MarshalAs(UnmanagedType.I4)] out int value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_parse_int64( [MarshalAs(UnmanagedType.I8)] out long value, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] - internal static extern int git_config_set_bool( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_bool( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.Bool)] bool value); - [DllImport(libgit2)] - internal static extern int git_config_set_int32( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_int32( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, int value); - [DllImport(libgit2)] - internal static extern int git_config_set_int64( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_int64( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, long value); - [DllImport(libgit2)] - internal static extern int git_config_set_string( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_set_string( + git_config* cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string value); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int config_foreach_callback( IntPtr entry, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_config_foreach( - ConfigurationSafeHandle cfg, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_foreach( + git_config* cfg, config_foreach_callback callback, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_iterator_glob_new( - out ConfigurationIteratorSafeHandle iter, - ConfigurationSafeHandle cfg, + out IntPtr iter, + IntPtr cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_config_next( out IntPtr entry, - ConfigurationIteratorSafeHandle iter); + IntPtr iter); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_config_iterator_free(IntPtr iter); - [DllImport(libgit2)] - internal static extern int git_config_snapshot(out ConfigurationSafeHandle @out, ConfigurationSafeHandle config); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_config_snapshot(out git_config* @out, git_config* config); // Ordinarily we would decorate the `url` parameter with the StrictUtf8Marshaler like we do everywhere // else, but apparently doing a native->managed callback with the 64-bit version of CLR 2.0 can // sometimes vomit when using a custom IMarshaler. So yeah, don't do that. If you need the url, // call StrictUtf8Marshaler.FromNative manually. See the discussion here: // http://social.msdn.microsoft.com/Forums/en-US/netfx64bit/thread/1eb746c6-d695-4632-8a9e-16c4fa98d481 + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_cred_acquire_cb( out IntPtr cred, IntPtr url, @@ -411,732 +589,945 @@ internal delegate int git_cred_acquire_cb( GitCredentialType allowed_types, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_cred_default_new(out IntPtr cred); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_cred_userpass_plaintext_new( out IntPtr cred, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string username, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string password); - - [DllImport(libgit2)] - internal static extern void git_diff_free(IntPtr diff); - - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_tree( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, - GitObjectSafeHandle newTree, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string username, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string password); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_cred_free(IntPtr cred); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_describe_commit( + out git_describe_result* describe, + git_object* committish, + ref GitDescribeOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_describe_format( + GitBuf buf, + git_describe_result* describe, + ref GitDescribeFormatOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_describe_result_free(git_describe_result* describe); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_diff_free(git_diff* diff); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_tree( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, + git_object* newTree, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_index( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_index( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, + git_index* index, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_merge( - DiffSafeHandle onto, - DiffSafeHandle from); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_merge( + git_diff* onto, + git_diff* from); - [DllImport(libgit2)] - internal static extern int git_diff_index_to_workdir( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_index_to_workdir( + out git_diff* diff, + git_repository* repo, + git_index* index, GitDiffOptions options); - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_workdir( - out DiffSafeHandle diff, - RepositorySafeHandle repo, - GitObjectSafeHandle oldTree, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_tree_to_workdir( + out git_diff* diff, + git_repository* repo, + git_object* oldTree, GitDiffOptions options); - internal delegate int git_diff_file_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_file_cb( + [In] git_diff_delta* delta, float progress, IntPtr payload); - internal delegate int git_diff_hunk_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_hunk_cb( + [In] git_diff_delta* delta, [In] GitDiffHunk hunk, IntPtr payload); - internal delegate int git_diff_line_cb( - [In] GitDiffDelta delta, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_line_cb( + [In] git_diff_delta* delta, [In] GitDiffHunk hunk, [In] GitDiffLine line, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_blobs( - GitObjectSafeHandle oldBlob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath old_as_path, - GitObjectSafeHandle newBlob, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath new_as_path, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_binary_cb( + [In] git_diff_delta* delta, + [In] GitDiffBinary binary, + IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_blobs( + git_object* oldBlob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string old_as_path, + git_object* newBlob, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string new_as_path, GitDiffOptions options, git_diff_file_cb fileCallback, + git_diff_binary_cb binaryCallback, git_diff_hunk_cb hunkCallback, git_diff_line_cb lineCallback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_foreach( - DiffSafeHandle diff, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_foreach( + git_diff* diff, git_diff_file_cb fileCallback, + git_diff_binary_cb binaryCallback, git_diff_hunk_cb hunkCallback, git_diff_line_cb lineCallback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_find_similar( - DiffSafeHandle diff, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_diff_find_similar( + git_diff* diff, GitDiffFindOptions options); - [DllImport(libgit2)] - internal static extern UIntPtr git_diff_num_deltas(DiffSafeHandle diff); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_diff_num_deltas(git_diff* diff); - [DllImport(libgit2)] - internal static extern IntPtr git_diff_get_delta(DiffSafeHandle diff, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_diff_delta* git_diff_get_delta(git_diff* diff, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_libgit2_features(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_filter_register( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + IntPtr gitFilter, int priority); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_filter_unregister( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_filter_source_mode(git_filter_source* source); - [DllImport(libgit2)] - internal static extern int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, RepositorySafeHandle repo, ref GitOid one, ref GitOid two); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_features(); - [DllImport(libgit2)] - internal static extern int git_graph_descendant_of( - RepositorySafeHandle repo, + #region git_libgit2_opts + + // Bindings for git_libgit2_opts(int option, ...): + // Currently only GIT_OPT_GET_SEARCH_PATH and GIT_OPT_SET_SEARCH_PATH are supported, + // but other overloads could be added using a similar pattern. + // CallingConvention.Cdecl is used to allow binding the the C varargs signature, and each possible call signature must be enumerated. + // __argslist was an option, but is an undocumented feature that should likely not be used here. + + // git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, uint level, GitBuf buf); + + // git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, uint level, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + // git_libgit2_opts(GIT_OPT_ENABLE_*, int enabled) + // git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, int enabled) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, int enabled); + + // git_libgit2_opts(GIT_OPT_SET_USER_AGENT, const char *path) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + // git_libgit2_opts(GIT_OPT_GET_USER_AGENT, git_buf *buf) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, GitBuf buf); + + // git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, const char **extensions, size_t len) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, IntPtr extensions, UIntPtr len); + + // git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, git_strarray *out) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_opts(int option, out GitStrArray extensions); + + // git_libgit2_opts(GIT_OPT_GET_OWNER_VALIDATION, int *enabled) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_libgit2_opts(int option, int* enabled); + #endregion + + #region git_libgit2_opts_osxarm64 + + // For RID osx-arm64 the calling convention is different: we need to pad out to 8 arguments before varargs + // (see discussion at https://github.com/dotnet/runtime/issues/48796) + + // git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, int level, git_buf *buf) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, uint level, GitBuf buf); + + // git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, int level, const char *path) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, uint level, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + // git_libgit2_opts(GIT_OPT_ENABLE_*, int enabled) + // git_libgit2_opts(GIT_OPT_SET_OWNER_VALIDATION, int enabled) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, int enabled); + + // git_libgit2_opts(GIT_OPT_SET_USER_AGENT, const char *path) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + // git_libgit2_opts(GIT_OPT_GET_USER_AGENT, git_buf *buf) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, GitBuf buf); + + // git_libgit2_opts(GIT_OPT_SET_EXTENSIONS, const char **extensions, size_t len) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, IntPtr extensions, UIntPtr len); + + // git_libgit2_opts(GIT_OPT_GET_EXTENSIONS, git_strarray *out) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, out GitStrArray extensions); + + // git_libgit2_opts(GIT_OPT_GET_OWNER_VALIDATION, int *enabled) + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl, EntryPoint = "git_libgit2_opts")] + internal static extern unsafe int git_libgit2_opts_osxarm64(int option, IntPtr nop2, IntPtr nop3, IntPtr nop4, IntPtr nop5, IntPtr nop6, IntPtr nop7, IntPtr nop8, int* enabled); + #endregion + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_graph_ahead_behind(out UIntPtr ahead, out UIntPtr behind, git_repository* repo, ref GitOid one, ref GitOid two); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_graph_descendant_of( + git_repository* repo, ref GitOid commit, ref GitOid ancestor); - [DllImport(libgit2)] - internal static extern int git_ignore_add_rule( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string rules); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_add_rule( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string rules); - [DllImport(libgit2)] - internal static extern int git_ignore_clear_internal_rules(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_clear_internal_rules(git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_ignore_path_is_ignored( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_path_is_ignored( out int ignored, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - - [DllImport(libgit2)] - internal static extern int git_index_add_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); - [DllImport(libgit2)] - internal static extern int git_index_add( - IndexSafeHandle index, - GitIndexEntry entry); - - [DllImport(libgit2)] - internal static extern int git_index_conflict_get( - out IndexEntrySafeHandle ancestor, - out IndexEntrySafeHandle ours, - out IndexEntrySafeHandle theirs, - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_add_bypath( + git_index* index, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - [DllImport(libgit2)] - internal static extern UIntPtr git_index_entrycount(IndexSafeHandle index); - - [DllImport(libgit2)] - internal static extern int git_index_entry_stage(IndexEntrySafeHandle indexentry); - - [DllImport(libgit2)] - internal static extern void git_index_free(IntPtr index); - - [DllImport(libgit2)] - internal static extern IndexEntrySafeHandle git_index_get_byindex(IndexSafeHandle index, UIntPtr n); - - [DllImport(libgit2)] - internal static extern IndexEntrySafeHandle git_index_get_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_add( + git_index* index, + git_index_entry* entry); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_conflict_get( + out git_index_entry* ancestor, + out git_index_entry* ours, + out git_index_entry* theirs, + git_index* index, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_conflict_iterator_new( + out git_index_conflict_iterator* iterator, + git_index* index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_conflict_next( + out git_index_entry* ancestor, + out git_index_entry* ours, + out git_index_entry* theirs, + git_index_conflict_iterator* iterator); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_index_conflict_iterator_free( + git_index_conflict_iterator* iterator); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_entrycount(git_index* index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_entry_stage(git_index_entry* indexentry); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_index_free(git_index* index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_entry* git_index_get_byindex(git_index* index, UIntPtr n); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_entry* git_index_get_bypath( + git_index* index, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path, int stage); - [DllImport(libgit2)] - internal static extern int git_index_has_conflicts(IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_has_conflicts(git_index* index); - [DllImport(libgit2)] - internal static extern uint git_index_name_entrycount(IndexSafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_name_entrycount(git_index* handle); - [DllImport(libgit2)] - internal static extern IndexNameEntrySafeHandle git_index_name_get_byindex(IndexSafeHandle handle, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_name_entry* git_index_name_get_byindex(git_index* handle, UIntPtr n); - [DllImport(libgit2)] - internal static extern int git_index_open( - out IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_open( + out git_index* index, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath indexpath); - [DllImport(libgit2)] - internal static extern int git_index_read( - IndexSafeHandle index, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_read( + git_index* index, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_index_remove_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_remove_bypath( + git_index* index, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_reuc_entrycount(git_index* handle); - [DllImport(libgit2)] - internal static extern uint git_index_reuc_entrycount(IndexSafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_reuc_entry* git_index_reuc_get_byindex(git_index* handle, UIntPtr n); - [DllImport(libgit2)] - internal static extern IndexReucEntrySafeHandle git_index_reuc_get_byindex(IndexSafeHandle handle, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_reuc_entry* git_index_reuc_get_bypath( + git_index* handle, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); - [DllImport(libgit2)] - internal static extern IndexReucEntrySafeHandle git_index_reuc_get_bypath( - IndexSafeHandle handle, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write(git_index* index); - [DllImport(libgit2)] - internal static extern int git_index_write(IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write_tree(out GitOid treeOid, git_index* index); - [DllImport(libgit2)] - internal static extern int git_index_write_tree(out GitOid treeOid, IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write_tree_to(out GitOid treeOid, git_index* index, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_index_read_tree(IndexSafeHandle index, GitObjectSafeHandle tree); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_read_tree(git_index* index, git_object* tree); - [DllImport(libgit2)] - internal static extern int git_index_clear(IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_clear(git_index* index); - [DllImport(libgit2)] - internal static extern int git_merge_base_many( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_many( out GitOid mergeBase, - RepositorySafeHandle repo, + git_repository* repo, int length, [In] GitOid[] input_array); - [DllImport(libgit2)] - internal static extern int git_merge_base_octopus( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_octopus( out GitOid mergeBase, - RepositorySafeHandle repo, + git_repository* repo, int length, [In] GitOid[] input_array); - [DllImport(libgit2)] - internal static extern int git_merge_head_from_ref( - out GitMergeHeadHandle mergehead, - RepositorySafeHandle repo, - ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_ref( + out git_annotated_commit* annotatedCommit, + git_repository* repo, + git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_merge_head_from_fetchhead( - out GitMergeHeadHandle mergehead, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_fetchhead( + out git_annotated_commit* annotatedCommit, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string branch_name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_url, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_merge_head_from_id( - out GitMergeHeadHandle mergehead, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_from_revspec( + out git_annotated_commit* annotatedCommit, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string revspec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_annotated_commit_lookup( + out git_annotated_commit* annotatedCommit, + git_repository* repo, ref GitOid id); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_merge_head_id( - GitMergeHeadHandle mergeHead); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_annotated_commit_id( + git_annotated_commit* annotatedCommit); - [DllImport(libgit2)] - internal static extern int git_merge( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge( + git_repository* repo, [In] IntPtr[] their_heads, UIntPtr their_heads_len, ref GitMergeOpts merge_opts, ref GitCheckoutOpts checkout_opts); - [DllImport(libgit2)] - internal static extern int git_merge_analysis( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_commits( + out git_index* index, + git_repository* repo, + git_object* our_commit, + git_object* their_commit, + ref GitMergeOpts merge_opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_analysis( out GitMergeAnalysis status_out, out GitMergePreference preference_out, - RepositorySafeHandle repo, + git_repository* repo, [In] IntPtr[] their_heads, int their_heads_len); - [DllImport(libgit2)] - internal static extern void git_merge_head_free( - IntPtr merge_head); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_annotated_commit_free(git_annotated_commit* commit); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_message_prettify( GitBuf buf, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, [MarshalAs(UnmanagedType.Bool)] bool strip_comments, sbyte comment_char); - [DllImport(libgit2)] - internal static extern int git_note_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_create( out GitOid noteOid, - RepositorySafeHandle repo, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, + git_signature* author, + git_signature* committer, ref GitOid oid, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string note, int force); - [DllImport(libgit2)] - internal static extern void git_note_free(IntPtr note); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_note_free(git_note* note); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_note_message(NoteSafeHandle note); + internal static extern unsafe string git_note_message(git_note* note); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_note_id(NoteSafeHandle note); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_note_id(git_note* note); - [DllImport(libgit2)] - internal static extern int git_note_read( - out NoteSafeHandle note, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_read( + out git_note* note, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_note_remove( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_remove( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, - SignatureSafeHandle author, - SignatureSafeHandle committer, + git_signature* author, + git_signature* committer, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_note_default_ref( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string notes_ref, - RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_default_ref( + GitBuf notes_ref, + git_repository* repo); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_note_foreach_cb( ref GitOid blob_id, ref GitOid annotated_object_id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_note_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_note_foreach( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string notes_ref, git_note_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_add_backend(ObjectDatabaseSafeHandle odb, IntPtr backend, int priority); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_add_backend(git_odb* odb, IntPtr backend, int priority); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len); - [DllImport(libgit2)] - internal static extern int git_odb_exists(ObjectDatabaseSafeHandle odb, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_exists(git_odb* odb, ref GitOid id); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_odb_foreach_cb( IntPtr id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_foreach( - ObjectDatabaseSafeHandle odb, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_foreach( + git_odb* odb, git_odb_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_odb_open_wstream(out OdbStreamSafeHandle stream, ObjectDatabaseSafeHandle odb, UIntPtr size, GitObjectType type); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_open_wstream(out git_odb_stream* stream, git_odb* odb, long size, GitObjectType type); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_odb_free(git_odb* odb); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_read_header(out UIntPtr len_out, out GitObjectType type, git_odb* odb, ref GitOid id); - [DllImport(libgit2)] - internal static extern void git_odb_free(IntPtr odb); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_object_free(git_object* obj); - [DllImport(libgit2)] - internal static extern void git_object_free(IntPtr obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_stream_write(git_odb_stream* Stream, IntPtr Buffer, UIntPtr len); - [DllImport(libgit2)] - internal static extern int git_odb_stream_write(OdbStreamSafeHandle Stream, IntPtr Buffer, UIntPtr len); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_stream_finalize_write(out GitOid id, git_odb_stream* stream); - [DllImport(libgit2)] - internal static extern int git_odb_stream_finalize_write(out GitOid id, OdbStreamSafeHandle stream); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_odb_stream_free(git_odb_stream* stream); - [DllImport(libgit2)] - internal static extern void git_odb_stream_free(IntPtr stream); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_write(out GitOid id, git_odb* odb, byte* data, UIntPtr len, GitObjectType type); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_object_id(GitObjectSafeHandle obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_object_id(git_object* obj); - [DllImport(libgit2)] - internal static extern int git_object_lookup(out GitObjectSafeHandle obj, RepositorySafeHandle repo, ref GitOid id, GitObjectType type); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_object_lookup(out git_object* obj, git_repository* repo, ref GitOid id, GitObjectType type); - [DllImport(libgit2)] - internal static extern int git_object_peel( - out GitObjectSafeHandle peeled, - GitObjectSafeHandle obj, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_object_peel( + out git_object* peeled, + git_object* obj, GitObjectType type); - [DllImport(libgit2)] - internal static extern int git_object_short_id( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_object_short_id( GitBuf buf, - GitObjectSafeHandle obj); + git_object* obj); - [DllImport(libgit2)] - internal static extern GitObjectType git_object_type(GitObjectSafeHandle obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_object_type(git_object* obj); - [DllImport(libgit2)] - internal static extern int git_patch_from_diff(out PatchSafeHandle patch, DiffSafeHandle diff, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_from_diff(out git_patch* patch, git_diff* diff, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_patch_print(PatchSafeHandle patch, git_diff_line_cb print_cb, IntPtr payload); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_print(git_patch* patch, git_diff_line_cb print_cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_patch_line_stats( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_patch_line_stats( out UIntPtr total_context, out UIntPtr total_additions, out UIntPtr total_deletions, - PatchSafeHandle patch); + git_patch* patch); - [DllImport(libgit2)] - internal static extern void git_patch_free(IntPtr patch); - - [DllImport(libgit2)] - internal static extern int git_push_new(out PushSafeHandle push, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_patch_free(git_patch* patch); /* Push network progress notification function */ + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_push_transfer_progress(uint current, uint total, UIntPtr bytes, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_packbuilder_progress(int stage, uint current, uint total, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_push_set_callbacks( - PushSafeHandle push, - git_packbuilder_progress pack_progress_cb, - IntPtr pack_progress_cb_payload, - git_push_transfer_progress transfer_progress_cb, - IntPtr transfer_progress_cb_payload); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_packbuilder_free(git_packbuilder* packbuilder); - [DllImport(libgit2)] - internal static extern int git_push_set_options(PushSafeHandle push, GitPushOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert( + git_packbuilder* packbuilder, + ref GitOid id, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert_commit( + git_packbuilder* packbuilder, + ref GitOid id); - [DllImport(libgit2)] - internal static extern int git_push_add_refspec( - PushSafeHandle push, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string pushRefSpec); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert_recur( + git_packbuilder* packbuilder, + ref GitOid id, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern int git_push_finish(PushSafeHandle push); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert_tree( + git_packbuilder* packbuilder, + ref GitOid id); - [DllImport(libgit2)] - internal static extern void git_push_free(IntPtr push); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_new(out git_packbuilder* packbuilder, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_push_status_foreach( - PushSafeHandle push, - push_status_foreach_cb status_cb, - IntPtr data); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_packbuilder_object_count(git_packbuilder* packbuilder); - internal delegate int push_status_foreach_cb( - IntPtr reference, - IntPtr msg, - IntPtr data); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_packbuilder_set_threads(git_packbuilder* packbuilder, uint numThreads); - [DllImport(libgit2)] - internal static extern int git_push_unpack_ok(PushSafeHandle push); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_write( + git_packbuilder* packbuilder, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, + uint mode, + IntPtr progressCallback, + IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_push_update_tips( - PushSafeHandle push, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_packbuilder_written(git_packbuilder* packbuilder); - [DllImport(libgit2)] - internal static extern int git_reference_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_create( + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, ref GitOid oid, [MarshalAs(UnmanagedType.Bool)] bool force, - SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern int git_reference_symbolic_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_symbolic_create( + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, [MarshalAs(UnmanagedType.Bool)] bool force, - SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int ref_glob_callback( IntPtr reference_name, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_reference_foreach_glob( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_foreach_glob( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string glob, ref_glob_callback callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern void git_reference_free(IntPtr reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_reference_free(git_reference* reference); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_reference_is_valid_name( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); - [DllImport(libgit2)] - internal static extern int git_reference_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_reference_lookup( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_lookup( + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reference_name(ReferenceSafeHandle reference); + internal static extern unsafe string git_reference_name(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_remove( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_remove( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reference_target(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reference_target(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_rename( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_rename( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string newName, [MarshalAs(UnmanagedType.Bool)] bool force, - SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern int git_reference_set_target( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_set_target( + out git_reference* ref_out, + git_reference* reference, ref GitOid id, - SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern int git_reference_symbolic_set_target( - out ReferenceSafeHandle ref_out, - ReferenceSafeHandle reference, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_symbolic_set_target( + out git_reference* ref_out, + git_reference* reference, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string target, - SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reference_symbolic_target(ReferenceSafeHandle reference); + internal static extern unsafe string git_reference_symbolic_target(git_reference* reference); - [DllImport(libgit2)] - internal static extern GitReferenceType git_reference_type(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitReferenceType git_reference_type(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_ensure_log( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof (StrictUtf8Marshaler))] string refname); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_ensure_log( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); - [DllImport(libgit2)] - internal static extern void git_reflog_free( - IntPtr reflog); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_reflog_free(git_reflog* reflog); - [DllImport(libgit2)] - internal static extern int git_reflog_read( - out ReflogSafeHandle ref_out, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reflog_read( + out git_reflog* ref_out, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern UIntPtr git_reflog_entrycount - (ReflogSafeHandle reflog); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_reflog_entrycount + (git_reflog* reflog); - [DllImport(libgit2)] - internal static extern ReflogEntrySafeHandle git_reflog_entry_byindex( - ReflogSafeHandle reflog, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_reflog_entry* git_reflog_entry_byindex( + git_reflog* reflog, UIntPtr idx); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reflog_entry_id_old( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reflog_entry_id_old( + git_reflog_entry* entry); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reflog_entry_id_new( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reflog_entry_id_new( + git_reflog_entry* entry); - [DllImport(libgit2)] - internal static extern IntPtr git_reflog_entry_committer( - SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_reflog_entry_committer( + git_reflog_entry* entry); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_reflog_entry_message(SafeHandle entry); + internal static extern unsafe string git_reflog_entry_message(git_reflog_entry* entry); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_refspec_transform( + GitBuf buf, + IntPtr refspec, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_refspec_rtransform( GitBuf buf, - GitRefSpecHandle refSpec, + IntPtr refspec, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_string( - GitRefSpecHandle refSpec); + IntPtr refSpec); - [DllImport(libgit2)] - internal static extern RefSpecDirection git_refspec_direction(GitRefSpecHandle refSpec); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern RefSpecDirection git_refspec_direction(IntPtr refSpec); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_dst( - GitRefSpecHandle refSpec); + IntPtr refSpec); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] internal static extern string git_refspec_src( - GitRefSpecHandle refSpec); - - [DllImport(libgit2)] - internal static extern bool git_refspec_force(GitRefSpecHandle refSpec); - - [DllImport(libgit2)] - internal static extern int git_remote_autotag(RemoteSafeHandle remote); - - [DllImport(libgit2)] - internal static extern int git_remote_connect(RemoteSafeHandle remote, GitDirection direction); - - [DllImport(libgit2)] - internal static extern int git_remote_create( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + IntPtr refspec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool git_refspec_force(IntPtr refSpec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool git_refspec_src_matches( + IntPtr refspec, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string reference); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern bool git_refspec_dst_matches( + IntPtr refspec, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string reference); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_autotag(git_remote* remote); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_connect( + git_remote* remote, + GitDirection direction, + ref GitRemoteCallbacks callbacks, + ref GitProxyOptions proxy_options, + ref GitStrArray custom_headers); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_create_anonymous( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create_anonymous( + out git_remote* remote, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_create_with_fetchspec( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_create_with_fetchspec( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refspec); - [DllImport(libgit2)] - internal static extern int git_remote_delete( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_delete( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - [DllImport(libgit2)] - internal static extern void git_remote_disconnect(RemoteSafeHandle remote); - - [DllImport(libgit2)] - internal static extern int git_remote_fetch( - RemoteSafeHandle remote, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_fetch( + git_remote* remote, ref GitStrArray refspecs, - SignatureSafeHandle signature, + GitFetchOptions fetch_opts, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern void git_remote_free(IntPtr remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_remote_free(git_remote* remote); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_get_fetch_refspecs(out GitStrArray array, git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_get_fetch_refspecs(out GitStrArray array, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_refspec* git_remote_get_refspec(git_remote* remote, UIntPtr n); - [DllImport(libgit2)] - internal static extern GitRefSpecHandle git_remote_get_refspec(RemoteSafeHandle remote, UIntPtr n); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_get_push_refspecs(out GitStrArray array, git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_get_push_refspecs(out GitStrArray array, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_push( + git_remote* remote, + ref GitStrArray refSpecs, + GitPushOptions opts); - [DllImport(libgit2)] - internal static extern UIntPtr git_remote_refspec_count(RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_remote_refspec_count(git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_set_fetch_refspecs(RemoteSafeHandle remote, ref GitStrArray array); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_set_url( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_add_fetch( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_set_push_refspecs(RemoteSafeHandle remote, ref GitStrArray array); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_set_pushurl( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] - internal static extern int git_remote_set_url( - RemoteSafeHandle remote, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_add_push( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_remote_is_valid_name( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string remote_name); - [DllImport(libgit2)] - internal static extern int git_remote_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_remote_load( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_lookup( + out git_remote* remote, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); - internal delegate int git_headlist_cb(ref GitRemoteHead remoteHeadPtr, IntPtr payload); - - [DllImport(libgit2)] - internal static extern int git_remote_ls(out IntPtr heads, out UIntPtr size, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_ls(out git_remote_head** heads, out UIntPtr size, git_remote* remote); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_remote_name(RemoteSafeHandle remote); - - [DllImport(libgit2)] - internal static extern int git_remote_save(RemoteSafeHandle remote); + internal static extern unsafe string git_remote_name(git_remote* remote); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_remote_url(RemoteSafeHandle remote); + internal static extern unsafe string git_remote_url(git_remote* remote); - [DllImport(libgit2)] - internal static extern void git_remote_set_autotag(RemoteSafeHandle remote, TagFetchMode option); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_remote_pushurl(git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_set_callbacks( - RemoteSafeHandle remote, - ref GitRemoteCallbacks callbacks); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_remote_set_autotag( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + TagFetchMode option); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_progress_callback(IntPtr str, int len, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_completion_callback(RemoteCompletionType type, IntPtr data); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int remote_update_tips_callback( IntPtr refName, ref GitOid oldId, ref GitOid newId, IntPtr data); - [DllImport(libgit2)] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int push_negotiation_callback( + IntPtr updates, + UIntPtr len, + IntPtr payload); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int push_update_reference_callback( + IntPtr refName, + IntPtr status, + IntPtr data + ); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_repository_discover( GitBuf buf, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath start_path, [MarshalAs(UnmanagedType.Bool)] bool across_fs, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath ceiling_dirs); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_repository_fetchhead_foreach_cb( IntPtr remote_name, IntPtr remote_url, @@ -1144,428 +1535,590 @@ internal delegate int git_repository_fetchhead_foreach_cb( [MarshalAs(UnmanagedType.Bool)] bool is_merge, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_repository_fetchhead_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_fetchhead_foreach( + git_repository* repo, git_repository_fetchhead_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern void git_repository_free(IntPtr repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_repository_free(git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_head_detached(IntPtr repo); - [DllImport(libgit2)] - internal static extern int git_repository_head_detached(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_head_unborn(IntPtr repo); - [DllImport(libgit2)] - internal static extern int git_repository_head_unborn(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_ident( + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string email, + git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_index(out IndexSafeHandle index, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_index(out git_index* index, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_init_ext( - out RepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_init_ext( + out git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, GitRepositoryInitOptions options); - [DllImport(libgit2)] - internal static extern int git_repository_is_bare(RepositorySafeHandle handle); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_bare(IntPtr handle); - [DllImport(libgit2)] - internal static extern int git_repository_is_shallow(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_shallow(IntPtr repo); - [DllImport(libgit2)] - internal static extern int git_repository_state_cleanup(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_state_cleanup(git_repository* repo); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_repository_mergehead_foreach_cb( ref GitOid oid, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_repository_mergehead_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_mergehead_foreach( + git_repository* repo, git_repository_mergehead_foreach_cb cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_repository_message( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_message( GitBuf buf, - RepositorySafeHandle repository); + git_repository* repository); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_new( + out git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_odb(out ObjectDatabaseSafeHandle odb, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_odb(out git_odb* odb, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_repository_open( - out RepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_open( + out git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - [DllImport(libgit2)] - internal static extern int git_repository_open_ext( - NullRepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_open_ext( + out git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path, RepositoryOpenFlags flags, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath ceilingDirs); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_path(RepositorySafeHandle repository); + internal static extern unsafe FilePath git_repository_path(git_repository* repository); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_config( + git_repository* repository, + git_config* config); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_ident( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email); - [DllImport(libgit2)] - internal static extern void git_repository_set_config( - RepositorySafeHandle repository, - ConfigurationSafeHandle config); - [DllImport(libgit2)] - internal static extern void git_repository_set_index( - RepositorySafeHandle repository, - IndexSafeHandle index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_index( + git_repository* repository, + git_index* index); - [DllImport(libgit2)] - internal static extern int git_repository_set_workdir( - RepositorySafeHandle repository, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_workdir( + git_repository* repository, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath workdir, [MarshalAs(UnmanagedType.Bool)] bool update_gitlink); - [DllImport(libgit2)] - internal static extern int git_repository_set_head_detached( - RepositorySafeHandle repo, - ref GitOid commitish, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_head_detached( + git_repository* repo, + ref GitOid commitish); - [DllImport(libgit2)] - internal static extern int git_repository_set_head( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_head_detached_from_annotated( + git_repository* repo, + git_annotated_commit* commit); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_head( + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_state( + git_repository* repository); - [DllImport(libgit2)] - internal static extern int git_repository_state( - RepositorySafeHandle repository); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] + internal static extern unsafe FilePath git_repository_workdir(git_repository* repository); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_workdir(RepositorySafeHandle repository); + internal static extern FilePath git_repository_workdir(IntPtr repository); - [DllImport(libgit2)] - internal static extern int git_reset( - RepositorySafeHandle repo, - GitObjectSafeHandle target, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reset( + git_repository* repo, + git_object* target, ResetMode reset_type, - ref GitCheckoutOpts opts, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + ref GitCheckoutOpts opts); - [DllImport(libgit2)] - internal static extern int git_revert( - RepositorySafeHandle repo, - GitObjectSafeHandle commit, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revert( + git_repository* repo, + git_object* commit, GitRevertOpts opts); - [DllImport(libgit2)] - internal static extern int git_revparse_ext( - out GitObjectSafeHandle obj, - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revert_commit( + out git_index* index, + git_repository* repo, + git_object* revert_commit, + git_object* our_commit, + uint mainline, + ref GitMergeOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revparse_ext( + out git_object* obj, + out git_reference* reference, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string spec); - [DllImport(libgit2)] - internal static extern void git_revwalk_free(IntPtr walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_free(git_revwalk* walker); - [DllImport(libgit2)] - internal static extern int git_revwalk_hide(RevWalkerSafeHandle walker, ref GitOid commit_id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_hide(git_revwalk* walker, ref GitOid commit_id); - [DllImport(libgit2)] - internal static extern int git_revwalk_new(out RevWalkerSafeHandle walker, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_new(out git_revwalk* walker, git_repository* repo); - [DllImport(libgit2)] - internal static extern int git_revwalk_next(out GitOid id, RevWalkerSafeHandle walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_next(out GitOid id, git_revwalk* walker); - [DllImport(libgit2)] - internal static extern int git_revwalk_push(RevWalkerSafeHandle walker, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_push(git_revwalk* walker, ref GitOid id); - [DllImport(libgit2)] - internal static extern void git_revwalk_reset(RevWalkerSafeHandle walker); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_reset(git_revwalk* walker); - [DllImport(libgit2)] - internal static extern void git_revwalk_sorting(RevWalkerSafeHandle walk, CommitSortStrategies sort); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_sorting(git_revwalk* walk, CommitSortStrategies sort); - [DllImport(libgit2)] - internal static extern void git_revwalk_simplify_first_parent(RevWalkerSafeHandle walk); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_simplify_first_parent(git_revwalk* walk); - [DllImport(libgit2)] - internal static extern void git_signature_free(IntPtr signature); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_signature_free(git_signature* signature); - [DllImport(libgit2)] - internal static extern int git_signature_new( - out SignatureSafeHandle signature, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_signature_new( + out git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email, long time, int offset); - [DllImport(libgit2)] - internal static extern int git_signature_dup(out IntPtr dest, IntPtr sig); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_signature_now( + out git_signature* signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string email); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_signature_dup(out git_signature* dest, git_signature* sig); - [DllImport(libgit2)] - internal static extern int git_stash_save( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_save( out GitOid id, - RepositorySafeHandle repo, - SignatureSafeHandle stasher, + git_repository* repo, + git_signature* stasher, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, StashModifiers flags); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_stash_cb( UIntPtr index, IntPtr message, ref GitOid stash_id, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_stash_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_foreach( + git_repository* repo, git_stash_cb callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_stash_drop(RepositorySafeHandle repo, UIntPtr index); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_drop(git_repository* repo, UIntPtr index); - [DllImport(libgit2)] - internal static extern int git_status_file( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_apply( + git_repository* repo, + UIntPtr index, + GitStashApplyOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_pop( + git_repository* repo, + UIntPtr index, + GitStashApplyOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_file( out FileStatus statusflags, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath filepath); - [DllImport(libgit2)] - internal static extern int git_status_list_new( - out StatusListSafeHandle git_status_list, - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_list_new( + out git_status_list* git_status_list, + git_repository* repo, GitStatusOptions options); - [DllImport(libgit2)] - internal static extern int git_status_list_entrycount( - StatusListSafeHandle statusList); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_list_entrycount( + git_status_list* statusList); - [DllImport(libgit2)] - internal static extern StatusEntrySafeHandle git_status_byindex( - StatusListSafeHandle list, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_status_entry* git_status_byindex( + git_status_list* list, UIntPtr idx); - [DllImport(libgit2)] - internal static extern void git_status_list_free( - IntPtr statusList); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_status_list_free( + git_status_list* statusList); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern void git_strarray_free( ref GitStrArray array); - [DllImport(libgit2)] - internal static extern int git_submodule_lookup( - out SubmoduleSafeHandle reference, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_lookup( + out git_submodule* reference, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_resolve_url( + GitBuf buf, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string url); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_update( + git_submodule* sm, + [MarshalAs(UnmanagedType.Bool)] bool init, + ref GitSubmoduleUpdateOptions submoduleUpdateOptions); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int submodule_callback( IntPtr sm, IntPtr name, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_submodule_foreach( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_foreach( + git_repository* repo, submodule_callback callback, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_submodule_add_to_index( - SubmoduleSafeHandle submodule, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_add_to_index( + git_submodule* submodule, [MarshalAs(UnmanagedType.Bool)] bool write_index); - [DllImport(libgit2)] - internal static extern int git_submodule_save( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_submodule_free(git_submodule* submodule); - [DllImport(libgit2)] - internal static extern void git_submodule_free( - IntPtr submodule); - - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_submodule_path( - SubmoduleSafeHandle submodule); + internal static extern unsafe string git_submodule_path( + git_submodule* submodule); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_submodule_url( - SubmoduleSafeHandle submodule); + internal static extern unsafe string git_submodule_url( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_index_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_index_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_head_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_head_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_submodule_wd_id( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_wd_id( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern SubmoduleIgnore git_submodule_ignore( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleIgnore git_submodule_ignore( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern SubmoduleUpdate git_submodule_update( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleUpdate git_submodule_update_strategy( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern bool git_submodule_fetch_recurse_submodules( - SubmoduleSafeHandle submodule); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules( + git_submodule* submodule); - [DllImport(libgit2)] - internal static extern int git_submodule_reload( - SubmoduleSafeHandle submodule, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_reload( + git_submodule* submodule, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_submodule_status( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_status( out SubmoduleStatus status, - SubmoduleSafeHandle submodule); + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath name, + GitSubmoduleIgnore ignore); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_init( + git_submodule* submodule, + [MarshalAs(UnmanagedType.Bool)] bool overwrite); - [DllImport(libgit2)] - internal static extern int git_tag_annotation_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_annotation_create( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, - SignatureSafeHandle signature, + git_object* target, + git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); - [DllImport(libgit2)] - internal static extern int git_tag_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_create( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, - SignatureSafeHandle signature, + git_object* target, + git_signature* signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_tag_create_lightweight( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_create_lightweight( out GitOid oid, - RepositorySafeHandle repo, + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, - GitObjectSafeHandle target, + git_object* target, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_tag_delete( - RepositorySafeHandle repo, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_delete( + git_repository* repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string tagName); - [DllImport(libgit2)] - internal static extern int git_tag_list(out GitStrArray array, RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_list(out GitStrArray array, git_repository* repo); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tag_message(GitObjectSafeHandle tag); + internal static extern unsafe string git_tag_message(git_object* tag); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tag_name(GitObjectSafeHandle tag); + internal static extern unsafe string git_tag_name(git_object* tag); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_tag_tagger(git_object* tag); - [DllImport(libgit2)] - internal static extern IntPtr git_tag_tagger(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tag_target_id(git_object* tag); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_tag_target_id(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tag_target_type(git_object* tag); - [DllImport(libgit2)] - internal static extern GitObjectType git_tag_target_type(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_init(); - [DllImport(libgit2)] - internal static extern int git_threads_init(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_shutdown(); - [DllImport(libgit2)] - internal static extern void git_threads_shutdown(); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_openssl_set_locking(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void git_trace_cb(LogLevel level, IntPtr message); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_trace_set(LogLevel level, git_trace_cb trace_cb); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload); - [DllImport(libgit2)] + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_transport_certificate_check_cb(git_certificate* cert, int valid, IntPtr hostname, IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_register( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix, IntPtr transport_cb, IntPtr payload); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_smart( out IntPtr transport, IntPtr remote, IntPtr definition); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transport_smart_certificate_check( + IntPtr transport, + IntPtr cert, + int valid, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string hostname); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transport_smart_credentials( + out IntPtr cred_out, + IntPtr transport, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string user, + int methods); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_transport_unregister( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string prefix); - [DllImport(libgit2)] - internal static extern uint git_tree_entry_filemode(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_tree_entry_filemode(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_tree_entry* git_tree_entry_byindex(git_object* tree, UIntPtr idx); - [DllImport(libgit2)] - internal static extern int git_tree_entry_bypath( - out TreeEntrySafeHandle_Owned tree, - GitObjectSafeHandle root, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath treeentry_path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tree_entry_bypath( + out git_tree_entry* tree, + git_object* root, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string treeentry_path); - [DllImport(libgit2)] - internal static extern void git_tree_entry_free(IntPtr treeEntry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_tree_entry_free(git_tree_entry* treeEntry); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_tree_entry_id(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tree_entry_id(git_tree_entry* entry); - [DllImport(libgit2)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] - internal static extern string git_tree_entry_name(SafeHandle entry); + internal static extern unsafe string git_tree_entry_name(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern GitObjectType git_tree_entry_type(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern UIntPtr git_tree_entrycount(GitObjectSafeHandle tree); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_tree_entrycount(git_object* tree); - [DllImport(libgit2)] - internal static extern int git_treebuilder_create(out TreeBuilderSafeHandle builder, IntPtr src); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_new(out git_treebuilder* builder, git_repository* repo, IntPtr src); - [DllImport(libgit2)] - internal static extern int git_treebuilder_insert( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_insert( IntPtr entry_out, - TreeBuilderSafeHandle builder, + git_treebuilder* builder, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string treeentry_name, ref GitOid id, uint attributes); - [DllImport(libgit2)] - internal static extern int git_treebuilder_write(out GitOid id, RepositorySafeHandle repo, TreeBuilderSafeHandle bld); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_write(out GitOid id, git_treebuilder* bld); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_treebuilder_free(git_treebuilder* bld); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_is_binary(git_object* blob); - [DllImport(libgit2)] - internal static extern void git_treebuilder_free(IntPtr bld); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_cherrypick(git_repository* repo, git_object* commit, GitCherryPickOptions options); - [DllImport(libgit2)] - internal static extern int git_blob_is_binary(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_cherrypick_commit(out git_index* index, + git_repository* repo, + git_object* cherrypick_commit, + git_object* our_commit, + uint mainline, + ref GitMergeOpts options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transaction_commit(IntPtr txn); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_transaction_free(IntPtr txn); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int url_resolve_callback( + IntPtr url_resolved, + IntPtr url, + int direction, + IntPtr payload); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_worktree_free(git_worktree* worktree); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_lookup( + out git_worktree* reference, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_list( + out GitStrArray array, + git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_open_from_worktree( + out git_repository* repository, + git_worktree* worktree); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_is_locked( + GitBuf reason, + git_worktree* worktree); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_validate( + git_worktree* worktree); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_lock( + git_worktree* worktree, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string reason); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_unlock( + git_worktree* worktree); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_add( + out git_worktree* reference, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path, + git_worktree_add_options options); - [DllImport(libgit2)] - internal static extern int git_cherrypick(RepositorySafeHandle repo, GitObjectSafeHandle commit, GitCherryPickOptions options); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_worktree_prune( + git_worktree* worktree, + git_worktree_prune_options options); } } -// ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/ObjectSafeWrapper.cs b/LibGit2Sharp/Core/ObjectSafeWrapper.cs index e7f610772..f2ab4a9e1 100644 --- a/LibGit2Sharp/Core/ObjectSafeWrapper.cs +++ b/LibGit2Sharp/Core/ObjectSafeWrapper.cs @@ -5,28 +5,30 @@ namespace LibGit2Sharp.Core { internal class ObjectSafeWrapper : IDisposable { - private readonly GitObjectSafeHandle objectPtr; + private readonly ObjectHandle objectPtr; - public ObjectSafeWrapper(ObjectId id, RepositorySafeHandle handle, bool allowNullObjectId = false) + public unsafe ObjectSafeWrapper(ObjectId id, RepositoryHandle handle, bool allowNullObjectId = false, bool throwIfMissing = false) { Ensure.ArgumentNotNull(handle, "handle"); if (allowNullObjectId && id == null) { - objectPtr = new NullGitObjectSafeHandle(); + objectPtr = new ObjectHandle(null, false); } else { Ensure.ArgumentNotNull(id, "id"); objectPtr = Proxy.git_object_lookup(handle, id, GitObjectType.Any); } - } - public GitObjectSafeHandle ObjectPtr - { - get { return objectPtr; } + if (objectPtr == null && throwIfMissing) + { + throw new NotFoundException($"No valid git object identified by '{id}' exists in the repository."); + } } + public ObjectHandle ObjectPtr => objectPtr; + public void Dispose() { Dispose(true); diff --git a/LibGit2Sharp/Core/Opaques.cs b/LibGit2Sharp/Core/Opaques.cs new file mode 100644 index 000000000..f83e8be10 --- /dev/null +++ b/LibGit2Sharp/Core/Opaques.cs @@ -0,0 +1,32 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal struct git_tree_entry { } + internal struct git_reference { } + internal struct git_refspec { } + internal struct git_repository { } + internal struct git_status_list { } + internal struct git_blame { } + internal struct git_diff { } + internal struct git_patch { } + internal struct git_config { } + internal struct git_index_conflict_iterator { } + internal struct git_index { } + internal struct git_reflog { } + internal struct git_reflog_entry { } + internal struct git_treebuilder { } + internal struct git_packbuilder { } + internal struct git_note { } + internal struct git_describe_result { } + internal struct git_submodule { } + internal struct git_annotated_commit { } + internal struct git_odb { } + internal struct git_revwalk { } + internal struct git_remote { } + internal struct git_object { } + internal struct git_rebase { } + internal struct git_odb_stream { } + internal struct git_worktree { } +} + diff --git a/LibGit2Sharp/Core/PathCase.cs b/LibGit2Sharp/Core/PathCase.cs index a5fadbb48..600f693de 100644 --- a/LibGit2Sharp/Core/PathCase.cs +++ b/LibGit2Sharp/Core/PathCase.cs @@ -16,6 +16,7 @@ public PathCase(IRepository repo) comparer = StringComparer.OrdinalIgnoreCase; comparison = StringComparison.OrdinalIgnoreCase; break; + default: comparer = StringComparer.Ordinal; comparison = StringComparison.Ordinal; diff --git a/LibGit2Sharp/Core/Platform.cs b/LibGit2Sharp/Core/Platform.cs new file mode 100644 index 000000000..1fcb59faf --- /dev/null +++ b/LibGit2Sharp/Core/Platform.cs @@ -0,0 +1,79 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal enum OperatingSystemType + { + Windows, + Unix, + MacOSX + } + + internal static class Platform + { + public static string ProcessorArchitecture => RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant(); + + public static OperatingSystemType OperatingSystem + { + get + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return OperatingSystemType.Windows; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return OperatingSystemType.Unix; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return OperatingSystemType.MacOSX; + } + + throw new PlatformNotSupportedException(); + } + } + + public static string GetNativeLibraryExtension() + { + switch (OperatingSystem) + { + case OperatingSystemType.MacOSX: + return ".dylib"; + + case OperatingSystemType.Unix: + return ".so"; + + case OperatingSystemType.Windows: + return ".dll"; + } + + throw new PlatformNotSupportedException(); + } + + /// + /// Returns true if the runtime is Mono. + /// + public static bool IsRunningOnMono() +#if NETFRAMEWORK + => Type.GetType("Mono.Runtime") != null; +#else + => false; +#endif + + /// + /// Returns true if the runtime is .NET Framework. + /// + public static bool IsRunningOnNetFramework() + => typeof(object).Assembly.GetName().Name == "mscorlib" && !IsRunningOnMono(); + + /// + /// Returns true if the runtime is .NET Core. + /// + public static bool IsRunningOnNetCore() + => typeof(object).Assembly.GetName().Name != "mscorlib"; + } +} diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index a04bb0bef..83d35e22c 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; -using System.Threading; +using System.Text; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; @@ -13,103 +14,68 @@ namespace LibGit2Sharp.Core { internal class Proxy { - #region giterr_ - - public static void giterr_set_str(GitErrorCategory error_class, Exception exception) - { - if (exception is OutOfMemoryException) - { - NativeMethods.giterr_set_oom(); - } - else - { - NativeMethods.giterr_set_str(error_class, exception.Message); - } - } - - public static void giterr_set_str(GitErrorCategory error_class, String errorString) - { - NativeMethods.giterr_set_str(error_class, errorString); - } - - #endregion + internal static readonly bool isOSXArm64 = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 + && RuntimeInformation.IsOSPlatform(OSPlatform.OSX); #region git_blame_ - public static BlameSafeHandle git_blame_file( - RepositorySafeHandle repo, - FilePath path, - GitBlameOptions options) - { - using (ThreadAffinity()) - { - BlameSafeHandle handle; - int res = NativeMethods.git_blame_file(out handle, repo, path, options); - Ensure.ZeroResult(res); - return handle; - } - } - - public static GitBlameHunk git_blame_get_hunk_byindex(BlameSafeHandle blame, uint idx) + public static unsafe BlameHandle git_blame_file( + RepositoryHandle repo, + string path, + git_blame_options options) { - return NativeMethods.git_blame_get_hunk_byindex(blame, idx).MarshalAs(false); + git_blame* ptr; + int res = NativeMethods.git_blame_file(out ptr, repo, path, options); + Ensure.ZeroResult(res); + return new BlameHandle(ptr, true); } - public static void git_blame_free(IntPtr blame) + public static unsafe git_blame_hunk* git_blame_get_hunk_byindex(BlameHandle blame, uint idx) { - NativeMethods.git_blame_free(blame); + return NativeMethods.git_blame_get_hunk_byindex(blame, idx); } #endregion #region git_blob_ - public static ObjectId git_blob_create_fromchunks(RepositorySafeHandle repo, FilePath hintpath, NativeMethods.source_callback fileCallback) + public static unsafe IntPtr git_blob_create_from_stream(RepositoryHandle repo, string hintpath) { - using (ThreadAffinity()) - { - var oid = new GitOid(); - int res = NativeMethods.git_blob_create_fromchunks(ref oid, repo, hintpath, fileCallback, IntPtr.Zero); - - if (res == (int)GitErrorCode.User) - { - throw new EndOfStreamException("The stream ended unexpectedly"); - } + IntPtr writestream_ptr; - Ensure.ZeroResult(res); + Ensure.ZeroResult(NativeMethods.git_blob_create_from_stream(out writestream_ptr, repo, hintpath)); + return writestream_ptr; + } - return oid; - } + public static unsafe ObjectId git_blob_create_fromstream_commit(IntPtr writestream_ptr) + { + var oid = new GitOid(); + Ensure.ZeroResult(NativeMethods.git_blob_create_from_stream_commit(ref oid, writestream_ptr)); + return oid; } - public static ObjectId git_blob_create_fromdisk(RepositorySafeHandle repo, FilePath path) + public static unsafe ObjectId git_blob_create_from_disk(RepositoryHandle repo, FilePath path) { - using (ThreadAffinity()) - { - var oid = new GitOid(); - int res = NativeMethods.git_blob_create_fromdisk(ref oid, repo, path); - Ensure.ZeroResult(res); + var oid = new GitOid(); + int res = NativeMethods.git_blob_create_from_disk(ref oid, repo, path); + Ensure.ZeroResult(res); - return oid; - } + return oid; } - public static ObjectId git_blob_create_fromfile(RepositorySafeHandle repo, FilePath path) + public static unsafe ObjectId git_blob_create_from_workdir(RepositoryHandle repo, FilePath path) { - using (ThreadAffinity()) - { - var oid = new GitOid(); - int res = NativeMethods.git_blob_create_fromworkdir(ref oid, repo, path); - Ensure.ZeroResult(res); + var oid = new GitOid(); + int res = NativeMethods.git_blob_create_from_workdir(ref oid, repo, path); + Ensure.ZeroResult(res); - return oid; - } + return oid; } - public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositorySafeHandle repo, ObjectId id, FilePath path, bool check_for_binary_data) + public static unsafe UnmanagedMemoryStream git_blob_filtered_content_stream(RepositoryHandle repo, ObjectId id, string path, bool check_for_binary_data) { var buf = new GitBuf(); - var handle = new ObjectSafeWrapper(id, repo).ObjectPtr; + var handle = new ObjectSafeWrapper(id, repo, throwIfMissing: true).ObjectPtr; return new RawContentStream(handle, h => { @@ -120,18 +86,18 @@ public static UnmanagedMemoryStream git_blob_filtered_content_stream(RepositoryS new[] { buf }); } - public static UnmanagedMemoryStream git_blob_rawcontent_stream(RepositorySafeHandle repo, ObjectId id, Int64 size) + public static unsafe UnmanagedMemoryStream git_blob_rawcontent_stream(RepositoryHandle repo, ObjectId id, long size) { - var handle = new ObjectSafeWrapper(id, repo).ObjectPtr; - return new RawContentStream(handle, NativeMethods.git_blob_rawcontent, h => size); + var handle = new ObjectSafeWrapper(id, repo, throwIfMissing: true).ObjectPtr; + return new RawContentStream(handle, h => NativeMethods.git_blob_rawcontent(h), h => size); } - public static Int64 git_blob_rawsize(GitObjectSafeHandle obj) + public static unsafe long git_blob_rawsize(ObjectHandle obj) { return NativeMethods.git_blob_rawsize(obj); } - public static bool git_blob_is_binary(GitObjectSafeHandle obj) + public static unsafe bool git_blob_is_binary(ObjectHandle obj) { int res = NativeMethods.git_blob_is_binary(obj); Ensure.BooleanResult(res); @@ -143,46 +109,56 @@ public static bool git_blob_is_binary(GitObjectSafeHandle obj) #region git_branch_ - public static ReferenceSafeHandle git_branch_create(RepositorySafeHandle repo, string branch_name, ObjectId targetId, bool force, - Signature signature, string logMessage) + public static unsafe ReferenceHandle git_branch_create_from_annotated(RepositoryHandle repo, string branch_name, string targetIdentifier, bool force) { - using (ThreadAffinity()) - using (var osw = new ObjectSafeWrapper(targetId, repo)) - using (var sigHandle = signature.BuildHandle()) + git_reference* reference; + + using (var annotatedCommit = git_annotated_commit_from_revspec(repo, targetIdentifier)) { - ReferenceSafeHandle reference; - int res = NativeMethods.git_branch_create(out reference, repo, branch_name, osw.ObjectPtr, force, sigHandle, logMessage); + int res = NativeMethods.git_branch_create_from_annotated(out reference, repo, branch_name, annotatedCommit, force); Ensure.ZeroResult(res); - return reference; } + + return new ReferenceHandle(reference, true); } - public static void git_branch_delete(ReferenceSafeHandle reference) + public static unsafe void git_branch_delete(ReferenceHandle reference) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_branch_delete(reference); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_branch_delete(reference); + Ensure.ZeroResult(res); } public static IEnumerable git_branch_iterator(Repository repo, GitBranchType branchType) { - return git_iterator( - (out BranchIteratorSafeHandle iter_out) => - NativeMethods.git_branch_iterator_new(out iter_out, repo.Handle, branchType), - (BranchIteratorSafeHandle iter, out ReferenceSafeHandle ref_out, out int res) => + IntPtr iter; + var res = NativeMethods.git_branch_iterator_new(out iter, repo.Handle.AsIntPtr(), branchType); + Ensure.ZeroResult(res); + + try + { + while (true) + { + IntPtr refPtr = IntPtr.Zero; + GitBranchType _branchType; + res = NativeMethods.git_branch_next(out refPtr, out _branchType, iter); + if (res == (int)GitErrorCode.IterOver) { - GitBranchType type_out; - res = NativeMethods.git_branch_next(out ref_out, out type_out, iter); - return new { BranchType = type_out }; - }, - (handle, payload) => + yield break; + } + Ensure.ZeroResult(res); + + Reference reference; + using (var refHandle = new ReferenceHandle(refPtr, true)) { - var reference = Reference.BuildFromPtr(handle, repo); - return new Branch(repo, reference, reference.CanonicalName); + reference = Reference.BuildFromPtr(refHandle, repo); } - ); + yield return new Branch(repo, reference, reference.CanonicalName); + } + } + finally + { + NativeMethods.git_branch_iterator_free(iter); + } } public static void git_branch_iterator_free(IntPtr iter) @@ -190,28 +166,22 @@ public static void git_branch_iterator_free(IntPtr iter) NativeMethods.git_branch_iterator_free(iter); } - public static ReferenceSafeHandle git_branch_move(ReferenceSafeHandle reference, string new_branch_name, bool force, - Signature signature, string logMessage) + public static unsafe ReferenceHandle git_branch_move(ReferenceHandle reference, string new_branch_name, bool force) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - ReferenceSafeHandle ref_out; - int res = NativeMethods.git_branch_move(out ref_out, reference, new_branch_name, force, sigHandle, logMessage); - Ensure.ZeroResult(res); - return ref_out; - } + git_reference* ref_out; + int res = NativeMethods.git_branch_move(out ref_out, reference, new_branch_name, force); + Ensure.ZeroResult(res); + return new ReferenceHandle(ref_out, true); } - public static string git_branch_remote_name(RepositorySafeHandle repo, string canonical_branch_name, bool shouldThrowIfNotFound) + public static unsafe string git_branch_remote_name(RepositoryHandle repo, string canonical_branch_name, bool shouldThrowIfNotFound) { - using (ThreadAffinity()) using (var buf = new GitBuf()) { int res = NativeMethods.git_branch_remote_name(buf, repo, canonical_branch_name); if (!shouldThrowIfNotFound && - (res == (int) GitErrorCode.NotFound || res == (int) GitErrorCode.Ambiguous)) + (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous)) { return null; } @@ -221,13 +191,12 @@ public static string git_branch_remote_name(RepositorySafeHandle repo, string ca } } - public static string git_branch_upstream_name(RepositorySafeHandle handle, string canonicalReferenceName) + public static unsafe string git_branch_upstream_name(RepositoryHandle handle, string canonicalReferenceName) { - using (ThreadAffinity()) using (var buf = new GitBuf()) { int res = NativeMethods.git_branch_upstream_name(buf, handle, canonicalReferenceName); - if (res == (int) GitErrorCode.NotFound) + if (res == (int)GitErrorCode.NotFound) { return null; } @@ -241,21 +210,20 @@ public static string git_branch_upstream_name(RepositorySafeHandle handle, strin #region git_buf_ - public static void git_buf_free(GitBuf buf) + public static void git_buf_dispose(GitBuf buf) { - NativeMethods.git_buf_free(buf); + NativeMethods.git_buf_dispose(buf); } #endregion #region git_checkout_ - public static void git_checkout_tree( - RepositorySafeHandle repo, + public static unsafe void git_checkout_tree( + RepositoryHandle repo, ObjectId treeId, ref GitCheckoutOpts opts) { - using (ThreadAffinity()) using (var osw = new ObjectSafeWrapper(treeId, repo)) { int res = NativeMethods.git_checkout_tree(repo, osw.ObjectPtr, ref opts); @@ -263,62 +231,71 @@ public static void git_checkout_tree( } } - public static void git_checkout_index(RepositorySafeHandle repo, GitObjectSafeHandle treeish, ref GitCheckoutOpts opts) + public static unsafe void git_checkout_index(RepositoryHandle repo, ObjectHandle treeish, ref GitCheckoutOpts opts) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_checkout_index(repo, treeish, ref opts); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_checkout_index(repo, treeish, ref opts); + Ensure.ZeroResult(res); } #endregion #region git_cherry_pick_ - internal static void git_cherrypick(RepositorySafeHandle repo, ObjectId commit, GitCherryPickOptions options) + internal static unsafe void git_cherrypick(RepositoryHandle repo, ObjectId commit, GitCherryPickOptions options) { - using (ThreadAffinity()) using (var nativeCommit = git_object_lookup(repo, commit, GitObjectType.Commit)) { int res = NativeMethods.git_cherrypick(repo, nativeCommit, options); Ensure.ZeroResult(res); } } + + internal static unsafe IndexHandle git_cherrypick_commit(RepositoryHandle repo, ObjectHandle cherrypickCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_cherrypick_commit(out index, repo, cherrypickCommit, ourCommit, mainline, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + return new IndexHandle(index, true); + } #endregion #region git_clone_ - public static RepositorySafeHandle git_clone( + public static unsafe RepositoryHandle git_clone( string url, string workdir, ref GitCloneOptions opts) { - using (ThreadAffinity()) - { - RepositorySafeHandle repo; - int res = NativeMethods.git_clone(out repo, url, workdir, ref opts); - Ensure.ZeroResult(res); - return repo; - } + git_repository* repo; + int res = NativeMethods.git_clone(out repo, url, workdir, ref opts); + Ensure.ZeroResult(res); + return new RepositoryHandle(repo, true); } #endregion #region git_commit_ - public static Signature git_commit_author(GitObjectSafeHandle obj) + public static unsafe Signature git_commit_author(ObjectHandle obj) { return new Signature(NativeMethods.git_commit_author(obj)); } - public static Signature git_commit_committer(GitObjectSafeHandle obj) + public static unsafe Signature git_commit_committer(ObjectHandle obj) { return new Signature(NativeMethods.git_commit_committer(obj)); } - public static ObjectId git_commit_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_commit_create( + RepositoryHandle repo, string referenceName, Signature author, Signature committer, @@ -326,19 +303,24 @@ public static ObjectId git_commit_create( Tree tree, GitOid[] parentIds) { - using (ThreadAffinity()) - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) using (var parentPtrs = new ArrayMarshaler(parentIds)) { GitOid commitOid; var treeOid = tree.Id.Oid; - int res = NativeMethods.git_commit_create_from_ids( - out commitOid, repo, referenceName, authorHandle, - committerHandle, null, message, - ref treeOid, (UIntPtr)parentPtrs.Count, parentPtrs.ToArray()); + int res = NativeMethods.git_commit_create_from_ids(out commitOid, + repo, + referenceName, + authorHandle, + committerHandle, + null, + message, + ref treeOid, + (UIntPtr)parentPtrs.Count, + parentPtrs.ToArray()); Ensure.ZeroResult(res); @@ -346,27 +328,82 @@ public static ObjectId git_commit_create( } } - public static string git_commit_message(GitObjectSafeHandle obj) + public static unsafe string git_commit_create_buffer( + RepositoryHandle repo, + Signature author, + Signature committer, + string message, + Tree tree, + Commit[] parents) + { + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) + using (var treeHandle = Proxy.git_object_lookup(tree.repo.Handle, tree.Id, GitObjectType.Tree)) + using (var buf = new GitBuf()) + { + ObjectHandle[] handles = Array.Empty(); + try + { + handles = parents.Select(c => Proxy.git_object_lookup(c.repo.Handle, c.Id, GitObjectType.Commit)).ToArray(); + var ptrs = handles.Select(p => p.AsIntPtr()).ToArray(); + int res; + fixed (IntPtr* objs = ptrs) + { + res = NativeMethods.git_commit_create_buffer(buf, + repo, + authorHandle, + committerHandle, + null, + message, + treeHandle, + new UIntPtr((ulong)parents.LongCount()), + objs); + } + Ensure.ZeroResult(res); + } + finally + { + foreach (var handle in handles) + { + handle.Dispose(); + } + } + + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + + public static unsafe ObjectId git_commit_create_with_signature(RepositoryHandle repo, string commitContent, + string signature, string field) + { + GitOid id; + int res = NativeMethods.git_commit_create_with_signature(out id, repo, commitContent, signature, field); + Ensure.ZeroResult(res); + + return id; + } + + public static unsafe string git_commit_message(ObjectHandle obj) { return NativeMethods.git_commit_message(obj); } - public static string git_commit_summary(GitObjectSafeHandle obj) + public static unsafe string git_commit_summary(ObjectHandle obj) { return NativeMethods.git_commit_summary(obj); } - public static string git_commit_message_encoding(GitObjectSafeHandle obj) + public static unsafe string git_commit_message_encoding(ObjectHandle obj) { return NativeMethods.git_commit_message_encoding(obj); } - public static ObjectId git_commit_parent_id(GitObjectSafeHandle obj, uint i) + public static unsafe ObjectId git_commit_parent_id(ObjectHandle obj, uint i) { - return NativeMethods.git_commit_parent_id(obj, i).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_commit_parent_id(obj, i)); } - public static int git_commit_parentcount(RepositorySafeHandle repo, ObjectId id) + public static int git_commit_parentcount(RepositoryHandle repo, ObjectId id) { using (var obj = new ObjectSafeWrapper(id, repo)) { @@ -374,43 +411,70 @@ public static int git_commit_parentcount(RepositorySafeHandle repo, ObjectId id) } } - public static int git_commit_parentcount(ObjectSafeWrapper obj) + public static unsafe int git_commit_parentcount(ObjectSafeWrapper obj) { return (int)NativeMethods.git_commit_parentcount(obj.ObjectPtr); } - public static ObjectId git_commit_tree_id(GitObjectSafeHandle obj) + public static unsafe ObjectId git_commit_tree_id(ObjectHandle obj) + { + return ObjectId.BuildFromPtr(NativeMethods.git_commit_tree_id(obj)); + } + + public static unsafe SignatureInfo git_commit_extract_signature(RepositoryHandle repo, ObjectId id, string field) { - return NativeMethods.git_commit_tree_id(obj).MarshalAsObjectId(); + using (var signature = new GitBuf()) + using (var signedData = new GitBuf()) + { + var oid = id.Oid; + Ensure.ZeroResult(NativeMethods.git_commit_extract_signature(signature, signedData, repo, ref oid, field)); + + return new SignatureInfo() + { + Signature = LaxUtf8Marshaler.FromNative(signature.ptr, signature.size.ConvertToInt()), + SignedData = LaxUtf8Marshaler.FromNative(signedData.ptr, signedData.size.ConvertToInt()), + }; + } } #endregion #region git_config_ - public static void git_config_add_file_ondisk(ConfigurationSafeHandle config, FilePath path, ConfigurationLevel level) + public static unsafe void git_config_add_file_ondisk(ConfigurationHandle config, FilePath path, ConfigurationLevel level, RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_config_add_file_ondisk(config, path, (uint)level, true); - Ensure.ZeroResult(res); - } + // RepositoryHandle does implicit cast voodoo that is not null-safe, thus this explicit check + git_repository* repoHandle = (repo != null) ? (git_repository*)repo : null; + int res = NativeMethods.git_config_add_file_ondisk(config, path, (uint)level, repoHandle, true); + Ensure.ZeroResult(res); } - public static bool git_config_delete(ConfigurationSafeHandle config, string name) + public static unsafe bool git_config_delete(ConfigurationHandle config, string name) { - using (ThreadAffinity()) + int res = NativeMethods.git_config_delete_entry(config, name); + + if (res == (int)GitErrorCode.NotFound) { - int res = NativeMethods.git_config_delete_entry(config, name); + return false; + } - if (res == (int)GitErrorCode.NotFound) - { - return false; - } + Ensure.ZeroResult(res); + return true; + } - Ensure.ZeroResult(res); - return true; + const string anyValue = ".*"; + + public static unsafe bool git_config_delete_multivar(ConfigurationHandle config, string name) + { + int res = NativeMethods.git_config_delete_multivar(config, name, anyValue); + + if (res == (int)GitErrorCode.NotFound) + { + return false; } + + Ensure.ZeroResult(res); + return true; } public static FilePath git_config_find_global() @@ -428,181 +492,242 @@ public static FilePath git_config_find_xdg() return ConvertPath(NativeMethods.git_config_find_xdg); } - public static void git_config_free(IntPtr config) + public static FilePath git_config_find_programdata() { - NativeMethods.git_config_free(config); + return ConvertPath(NativeMethods.git_config_find_programdata); } - public static ConfigurationEntry git_config_get_entry(ConfigurationSafeHandle config, string key) + public static unsafe void git_config_free(git_config* config) { - GitConfigEntryHandle handle; + NativeMethods.git_config_free(config); + } + public static unsafe ConfigurationEntry git_config_get_entry(ConfigurationHandle config, string key) + { if (!configurationParser.ContainsKey(typeof(T))) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Generic Argument of type '{0}' is not supported.", typeof(T).FullName)); } - using (ThreadAffinity()) + GitConfigEntry* entry = null; + try { - var res = NativeMethods.git_config_get_entry(out handle, config, key); + var res = NativeMethods.git_config_get_entry(out entry, config, key); if (res == (int)GitErrorCode.NotFound) { return null; } Ensure.ZeroResult(res); + return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry->namePtr), + (T)configurationParser[typeof(T)](LaxUtf8Marshaler.FromNative(entry->valuePtr)), + (ConfigurationLevel)entry->level); + } + finally + { + NativeMethods.git_config_entry_free(entry); } - - GitConfigEntry entry = handle.MarshalAsGitConfigEntry(); - - return new ConfigurationEntry(LaxUtf8Marshaler.FromNative(entry.namePtr), - (T)configurationParser[typeof(T)](LaxUtf8Marshaler.FromNative(entry.valuePtr)), - (ConfigurationLevel)entry.level); } - public static ConfigurationSafeHandle git_config_new() + public static unsafe ConfigurationHandle git_config_new() { - using (ThreadAffinity()) - { - ConfigurationSafeHandle handle; - int res = NativeMethods.git_config_new(out handle); - Ensure.ZeroResult(res); + git_config* handle; + int res = NativeMethods.git_config_new(out handle); + Ensure.ZeroResult(res); - return handle; - } + return new ConfigurationHandle(handle, true); } - public static ConfigurationSafeHandle git_config_open_level(ConfigurationSafeHandle parent, ConfigurationLevel level) + public static unsafe ConfigurationHandle git_config_open_level(ConfigurationHandle parent, ConfigurationLevel level) { - using (ThreadAffinity()) - { - ConfigurationSafeHandle handle; - int res = NativeMethods.git_config_open_level(out handle, parent, (uint)level); + git_config* handle; + int res = NativeMethods.git_config_open_level(out handle, parent, (uint)level); - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + if (res == (int)GitErrorCode.NotFound) + { + return null; + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return handle; - } + return new ConfigurationHandle(handle, true); } public static bool git_config_parse_bool(string value) { - using (ThreadAffinity()) - { - bool outVal; - var res = NativeMethods.git_config_parse_bool(out outVal, value); + bool outVal; + var res = NativeMethods.git_config_parse_bool(out outVal, value); - Ensure.ZeroResult(res); - return outVal; - } + Ensure.ZeroResult(res); + return outVal; } public static int git_config_parse_int32(string value) { - using (ThreadAffinity()) - { - int outVal; - var res = NativeMethods.git_config_parse_int32(out outVal, value); + int outVal; + var res = NativeMethods.git_config_parse_int32(out outVal, value); - Ensure.ZeroResult(res); - return outVal; - } + Ensure.ZeroResult(res); + return outVal; } public static long git_config_parse_int64(string value) { - using (ThreadAffinity()) - { - long outVal; - var res = NativeMethods.git_config_parse_int64(out outVal, value); + long outVal; + var res = NativeMethods.git_config_parse_int64(out outVal, value); - Ensure.ZeroResult(res); - return outVal; - } + Ensure.ZeroResult(res); + return outVal; } - public static void git_config_set_bool(ConfigurationSafeHandle config, string name, bool value) + public static unsafe void git_config_set_bool(ConfigurationHandle config, string name, bool value) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_config_set_bool(config, name, value); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_config_set_bool(config, name, value); + Ensure.ZeroResult(res); } - public static void git_config_set_int32(ConfigurationSafeHandle config, string name, int value) + public static unsafe void git_config_set_int32(ConfigurationHandle config, string name, int value) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_config_set_int32(config, name, value); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_config_set_int32(config, name, value); + Ensure.ZeroResult(res); } - public static void git_config_set_int64(ConfigurationSafeHandle config, string name, long value) + public static unsafe void git_config_set_int64(ConfigurationHandle config, string name, long value) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_config_set_int64(config, name, value); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_config_set_int64(config, name, value); + Ensure.ZeroResult(res); } - public static void git_config_set_string(ConfigurationSafeHandle config, string name, string value) + public static unsafe void git_config_set_string(ConfigurationHandle config, string name, string value) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_config_set_string(config, name, value); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_config_set_string(config, name, value); + Ensure.ZeroResult(res); + } + + static readonly string non_existing_regex = Guid.NewGuid().ToString(); + + public static unsafe void git_config_add_string(ConfigurationHandle config, string name, string value) + { + int res = NativeMethods.git_config_set_multivar(config, name, non_existing_regex, value); + Ensure.ZeroResult(res); } - public static ICollection git_config_foreach( - ConfigurationSafeHandle config, + public static unsafe ICollection git_config_foreach( + ConfigurationHandle config, Func resultSelector) { return git_foreach(resultSelector, c => NativeMethods.git_config_foreach(config, (e, p) => c(e, p), IntPtr.Zero)); } public static IEnumerable> git_config_iterator_glob( - ConfigurationSafeHandle config, - string regexp, - Func> resultSelector) - { - return git_iterator( - (out ConfigurationIteratorSafeHandle iter) => - NativeMethods.git_config_iterator_glob_new(out iter, config, regexp), - (ConfigurationIteratorSafeHandle iter, out SafeHandleBase handle, out int res) => + ConfigurationHandle config, + string regexp) + { + IntPtr iter; + var res = NativeMethods.git_config_iterator_glob_new(out iter, config.AsIntPtr(), regexp); + Ensure.ZeroResult(res); + try + { + while (true) + { + IntPtr entry; + res = NativeMethods.git_config_next(out entry, iter); + if (res == (int)GitErrorCode.IterOver) { - handle = null; + yield break; + } + Ensure.ZeroResult(res); + + yield return Configuration.BuildConfigEntry(entry); + } + } + finally + { + NativeMethods.git_config_iterator_free(iter); + } + } + + public static unsafe ConfigurationHandle git_config_snapshot(ConfigurationHandle config) + { + git_config* handle; + int res = NativeMethods.git_config_snapshot(out handle, config); + Ensure.ZeroResult(res); + + return new ConfigurationHandle(handle, true); + } + + public static unsafe IntPtr git_config_lock(git_config* config) + { + IntPtr txn; + int res = NativeMethods.git_config_lock(out txn, config); + Ensure.ZeroResult(res); - IntPtr entry; - res = NativeMethods.git_config_next(out entry, iter); - return new { EntryPtr = entry }; - }, - (handle, payload) => resultSelector(payload.EntryPtr) - ); + return txn; } - public static void git_config_iterator_free(IntPtr iter) + #endregion + + #region git_cred_ + + public static void git_cred_free(IntPtr cred) { - NativeMethods.git_config_iterator_free(iter); + NativeMethods.git_cred_free(cred); } - public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandle config) + #endregion + + #region git_describe_ + + public static unsafe string git_describe_commit( + RepositoryHandle repo, + ObjectId committishId, + DescribeOptions options) { - using (ThreadAffinity()) + Ensure.ArgumentPositiveInt32(options.MinimumCommitIdAbbreviatedSize, "options.MinimumCommitIdAbbreviatedSize"); + + using (var osw = new ObjectSafeWrapper(committishId, repo)) { - ConfigurationSafeHandle handle; - int res = NativeMethods.git_config_snapshot(out handle, config); - Ensure.ZeroResult(res); + GitDescribeOptions opts = new GitDescribeOptions + { + Version = 1, + DescribeStrategy = options.Strategy, + MaxCandidatesTags = 10, + OnlyFollowFirstParent = options.OnlyFollowFirstParent, + ShowCommitOidAsFallback = options.UseCommitIdAsFallback, + }; + + DescribeResultHandle describeHandle = null; + + try + { + git_describe_result* result; + int res = NativeMethods.git_describe_commit(out result, osw.ObjectPtr, ref opts); + Ensure.ZeroResult(res); + describeHandle = new DescribeResultHandle(result, true); + + using (var buf = new GitBuf()) + { + GitDescribeFormatOptions formatOptions = new GitDescribeFormatOptions + { + Version = 1, + MinAbbreviatedSize = (uint)options.MinimumCommitIdAbbreviatedSize, + AlwaysUseLongFormat = options.AlwaysRenderLongFormat, + }; - return handle; + res = NativeMethods.git_describe_format(buf, describeHandle, ref formatOptions); + Ensure.ZeroResult(res); + + describeHandle.Dispose(); + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + finally + { + if (describeHandle != null) + { + describeHandle.Dispose(); + } + } } } @@ -610,8 +735,8 @@ public static ConfigurationSafeHandle git_config_snapshot(ConfigurationSafeHandl #region git_diff_ - public static void git_diff_blobs( - RepositorySafeHandle repo, + public static unsafe void git_diff_blobs( + RepositoryHandle repo, ObjectId oldBlob, ObjectId newBlob, GitDiffOptions options, @@ -619,135 +744,227 @@ public static void git_diff_blobs( NativeMethods.git_diff_hunk_cb hunkCallback, NativeMethods.git_diff_line_cb lineCallback) { - using (ThreadAffinity()) using (var osw1 = new ObjectSafeWrapper(oldBlob, repo, true)) using (var osw2 = new ObjectSafeWrapper(newBlob, repo, true)) { - int res = NativeMethods.git_diff_blobs( - osw1.ObjectPtr, null, osw2.ObjectPtr, null, - options, fileCallback, hunkCallback, lineCallback, IntPtr.Zero); + int res = NativeMethods.git_diff_blobs(osw1.ObjectPtr, + null, + osw2.ObjectPtr, + null, + options, + fileCallback, + null, + hunkCallback, + lineCallback, + IntPtr.Zero); Ensure.ZeroResult(res); } } - public static void git_diff_foreach( - DiffSafeHandle diff, + public static unsafe void git_diff_foreach( + git_diff* diff, NativeMethods.git_diff_file_cb fileCallback, NativeMethods.git_diff_hunk_cb hunkCallback, NativeMethods.git_diff_line_cb lineCallback) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_diff_foreach(diff, fileCallback, hunkCallback, lineCallback, IntPtr.Zero); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_diff_foreach(diff, fileCallback, null, hunkCallback, lineCallback, IntPtr.Zero); + Ensure.ZeroResult(res); } - public static DiffSafeHandle git_diff_tree_to_index( - RepositorySafeHandle repo, - IndexSafeHandle index, + public static unsafe DiffHandle git_diff_tree_to_index( + RepositoryHandle repo, + IndexHandle index, ObjectId oldTree, GitDiffOptions options) { - using (ThreadAffinity()) using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_index(out diff, repo, osw.ObjectPtr, index, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static void git_diff_free(IntPtr diff) - { - NativeMethods.git_diff_free(diff); - } - - public static void git_diff_merge(DiffSafeHandle onto, DiffSafeHandle from) + public static unsafe void git_diff_merge(DiffHandle onto, DiffHandle from) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_diff_merge(onto, from); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_diff_merge(onto, from); + Ensure.ZeroResult(res); } - public static DiffSafeHandle git_diff_tree_to_tree( - RepositorySafeHandle repo, + public static unsafe DiffHandle git_diff_tree_to_tree( + RepositoryHandle repo, ObjectId oldTree, ObjectId newTree, GitDiffOptions options) { - using (ThreadAffinity()) - using (var osw1 = new ObjectSafeWrapper(oldTree, repo, true)) - using (var osw2 = new ObjectSafeWrapper(newTree, repo, true)) + using (var osw1 = new ObjectSafeWrapper(oldTree, repo, true, throwIfMissing: true)) + using (var osw2 = new ObjectSafeWrapper(newTree, repo, true, throwIfMissing: true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_tree(out diff, repo, osw1.ObjectPtr, osw2.ObjectPtr, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static DiffSafeHandle git_diff_index_to_workdir( - RepositorySafeHandle repo, - IndexSafeHandle index, + public static unsafe DiffHandle git_diff_index_to_workdir( + RepositoryHandle repo, + IndexHandle index, GitDiffOptions options) { - using (ThreadAffinity()) - { - DiffSafeHandle diff; - int res = NativeMethods.git_diff_index_to_workdir(out diff, repo, index, options); - Ensure.ZeroResult(res); + git_diff* diff; + int res = NativeMethods.git_diff_index_to_workdir(out diff, repo, index, options); + Ensure.ZeroResult(res); - return diff; - } + return new DiffHandle(diff, true); } - public static DiffSafeHandle git_diff_tree_to_workdir( - RepositorySafeHandle repo, + public static unsafe DiffHandle git_diff_tree_to_workdir( + RepositoryHandle repo, ObjectId oldTree, GitDiffOptions options) { - using (ThreadAffinity()) using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) { - DiffSafeHandle diff; + git_diff* diff; int res = NativeMethods.git_diff_tree_to_workdir(out diff, repo, osw.ObjectPtr, options); Ensure.ZeroResult(res); - return diff; + return new DiffHandle(diff, true); } } - public static void git_diff_find_similar(DiffSafeHandle diff, GitDiffFindOptions options) + public static unsafe void git_diff_find_similar(DiffHandle diff, GitDiffFindOptions options) + { + int res = NativeMethods.git_diff_find_similar(diff, options); + Ensure.ZeroResult(res); + } + + public static unsafe int git_diff_num_deltas(DiffHandle diff) + { + return (int)NativeMethods.git_diff_num_deltas(diff); + } + + public static unsafe git_diff_delta* git_diff_get_delta(DiffHandle diff, int idx) + { + return NativeMethods.git_diff_get_delta(diff, (UIntPtr)idx); + } + + #endregion + + #region git_error_ + + public static int git_error_set_str(GitErrorCategory error_class, Exception exception) { - using (ThreadAffinity()) + if (exception is OutOfMemoryException) { - int res = NativeMethods.git_diff_find_similar(diff, options); - Ensure.ZeroResult(res); + NativeMethods.git_error_set_oom(); + return 0; + } + else + { + return NativeMethods.git_error_set_str(error_class, ErrorMessageFromException(exception)); } } - public static int git_diff_num_deltas(DiffSafeHandle diff) + public static int git_error_set_str(GitErrorCategory error_class, string errorString) { - return (int)NativeMethods.git_diff_num_deltas(diff); + return NativeMethods.git_error_set_str(error_class, errorString); + } + + /// + /// This method will take an exception and try to generate an error message + /// that captures the important messages of the error. + /// The formatting is a bit subjective. + /// + /// + /// + public static string ErrorMessageFromException(Exception ex) + { + StringBuilder sb = new StringBuilder(); + BuildErrorMessageFromException(sb, 0, ex); + return sb.ToString(); + } + + private static void BuildErrorMessageFromException(StringBuilder sb, int level, Exception ex) + { + string indent = new string(' ', level * 4); + sb.AppendFormat("{0}{1}", indent, ex.Message); + + if (ex is AggregateException) + { + AggregateException aggregateException = ((AggregateException)ex).Flatten(); + + if (aggregateException.InnerExceptions.Count == 1) + { + sb.AppendLine(); + sb.AppendLine(); + + sb.AppendFormat("{0}Contained Exception:{1}", indent, Environment.NewLine); + BuildErrorMessageFromException(sb, level + 1, aggregateException.InnerException); + } + else + { + sb.AppendLine(); + sb.AppendLine(); + + sb.AppendFormat("{0}Contained Exceptions:{1}", indent, Environment.NewLine); + for (int i = 0; i < aggregateException.InnerExceptions.Count; i++) + { + if (i != 0) + { + sb.AppendLine(); + sb.AppendLine(); + } + + BuildErrorMessageFromException(sb, level + 1, aggregateException.InnerExceptions[i]); + } + } + } + else if (ex.InnerException != null) + { + sb.AppendLine(); + sb.AppendLine(); + sb.AppendFormat("{0}Inner Exception:{1}", indent, Environment.NewLine); + BuildErrorMessageFromException(sb, level + 1, ex.InnerException); + } + } + + #endregion + + #region git_filter_ + + public static void git_filter_register(string name, IntPtr filterPtr, int priority) + { + int res = NativeMethods.git_filter_register(name, filterPtr, priority); + if (res == (int)GitErrorCode.Exists) + { + throw new EntryExistsException("A filter with the name '{0}' is already registered", name); + } + Ensure.ZeroResult(res); + } + + public static void git_filter_unregister(string name) + { + int res = NativeMethods.git_filter_unregister(name); + Ensure.ZeroResult(res); } - public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) + public static unsafe FilterMode git_filter_source_mode(git_filter_source* filterSource) { - return NativeMethods.git_diff_get_delta(diff, (UIntPtr) idx).MarshalAs(false); + var res = NativeMethods.git_filter_source_mode(filterSource); + return (FilterMode)res; } #endregion #region git_graph_ - public static Tuple git_graph_ahead_behind(RepositorySafeHandle repo, Commit first, Commit second) + public static unsafe Tuple git_graph_ahead_behind(RepositoryHandle repo, Commit first, Commit second) { if (first == null || second == null) { @@ -757,99 +974,79 @@ public static GitDiffDelta git_diff_get_delta(DiffSafeHandle diff, int idx) GitOid oid1 = first.Id.Oid; GitOid oid2 = second.Id.Oid; - using (ThreadAffinity()) - { - UIntPtr ahead; - UIntPtr behind; + UIntPtr ahead; + UIntPtr behind; - int res = NativeMethods.git_graph_ahead_behind(out ahead, out behind, repo, ref oid1, ref oid2); + int res = NativeMethods.git_graph_ahead_behind(out ahead, out behind, repo, ref oid1, ref oid2); - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return new Tuple((int)ahead, (int)behind); - } + return new Tuple((int)ahead, (int)behind); } - public static bool git_graph_descendant_of(RepositorySafeHandle repo, ObjectId commitId, ObjectId ancestorId) + public static unsafe bool git_graph_descendant_of(RepositoryHandle repo, ObjectId commitId, ObjectId ancestorId) { GitOid oid1 = commitId.Oid; GitOid oid2 = ancestorId.Oid; + int res = NativeMethods.git_graph_descendant_of(repo, ref oid1, ref oid2); - using (ThreadAffinity()) - { - int res = NativeMethods.git_graph_descendant_of(repo, ref oid1, ref oid2); - - Ensure.BooleanResult(res); + Ensure.BooleanResult(res); - return (res == 1); - } + return (res == 1); } #endregion #region git_ignore_ - public static void git_ignore_add_rule(RepositorySafeHandle repo, string rules) + public static unsafe void git_ignore_add_rule(RepositoryHandle repo, string rules) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_ignore_add_rule(repo, rules); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_ignore_add_rule(repo, rules); + Ensure.ZeroResult(res); } - public static void git_ignore_clear_internal_rules(RepositorySafeHandle repo) + public static unsafe void git_ignore_clear_internal_rules(RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_ignore_clear_internal_rules(repo); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_ignore_clear_internal_rules(repo); + Ensure.ZeroResult(res); } - public static bool git_ignore_path_is_ignored(RepositorySafeHandle repo, string path) + public static unsafe bool git_ignore_path_is_ignored(RepositoryHandle repo, string path) { - using (ThreadAffinity()) - { - int ignored; - int res = NativeMethods.git_ignore_path_is_ignored(out ignored, repo, path); - Ensure.ZeroResult(res); + int ignored; + int res = NativeMethods.git_ignore_path_is_ignored(out ignored, repo, path); + Ensure.ZeroResult(res); - return (ignored != 0); - } + return (ignored != 0); } #endregion #region git_index_ - public static void git_index_add(IndexSafeHandle index, GitIndexEntry entry) + public static unsafe void git_index_add(IndexHandle index, git_index_entry* entry) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_add(index, entry); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_add(index, entry); + Ensure.ZeroResult(res); } - public static void git_index_add_bypath(IndexSafeHandle index, FilePath path) + public static unsafe void git_index_add_bypath(IndexHandle index, FilePath path) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_add_bypath(index, path); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_add_bypath(index, path); + Ensure.ZeroResult(res); } - public static Conflict git_index_conflict_get( - IndexSafeHandle index, - Repository repo, - FilePath path) + public static unsafe Conflict git_index_conflict_get( + IndexHandle index, + string path) { - IndexEntrySafeHandle ancestor, ours, theirs; + git_index_entry* ancestor, ours, theirs; - int res = NativeMethods.git_index_conflict_get( - out ancestor, out ours, out theirs, index, path); + int res = NativeMethods.git_index_conflict_get(out ancestor, + out ours, + out theirs, + index, + path); if (res == (int)GitErrorCode.NotFound) { @@ -858,45 +1055,60 @@ public static Conflict git_index_conflict_get( Ensure.ZeroResult(res); - return new Conflict( - IndexEntry.BuildFromPtr(ancestor), - IndexEntry.BuildFromPtr(ours), - IndexEntry.BuildFromPtr(theirs)); + return new Conflict(IndexEntry.BuildFromPtr(ancestor), + IndexEntry.BuildFromPtr(ours), + IndexEntry.BuildFromPtr(theirs)); + } + + public static unsafe ConflictIteratorHandle git_index_conflict_iterator_new(IndexHandle index) + { + git_index_conflict_iterator* iter; + int res = NativeMethods.git_index_conflict_iterator_new(out iter, index); + Ensure.ZeroResult(res); + + return new ConflictIteratorHandle(iter, true); } - public static int git_index_entrycount(IndexSafeHandle index) + public static unsafe Conflict git_index_conflict_next(ConflictIteratorHandle iterator) { - UIntPtr count = NativeMethods.git_index_entrycount(index); - if ((long)count > int.MaxValue) + git_index_entry* ancestor, ours, theirs; + + int res = NativeMethods.git_index_conflict_next(out ancestor, out ours, out theirs, iterator); + + if (res == (int)GitErrorCode.IterOver) { - throw new LibGit2SharpException("Index entry count exceeds size of int"); + return null; } - return (int)count; + + Ensure.ZeroResult(res); + + return new Conflict(IndexEntry.BuildFromPtr(ancestor), + IndexEntry.BuildFromPtr(ours), + IndexEntry.BuildFromPtr(theirs)); } - public static StageLevel git_index_entry_stage(IndexEntrySafeHandle index) + public static unsafe int git_index_entrycount(IndexHandle index) { - return (StageLevel)NativeMethods.git_index_entry_stage(index); + return NativeMethods.git_index_entrycount(index) + .ConvertToInt(); } - public static void git_index_free(IntPtr index) + public static unsafe StageLevel git_index_entry_stage(git_index_entry* entry) { - NativeMethods.git_index_free(index); + return (StageLevel)NativeMethods.git_index_entry_stage(entry); } - public static IndexEntrySafeHandle git_index_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_entry* git_index_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_get_byindex(index, n); } - public static IndexEntrySafeHandle git_index_get_bypath(IndexSafeHandle index, FilePath path, int stage) + public static unsafe git_index_entry* git_index_get_bypath(IndexHandle index, string path, int stage) { - IndexEntrySafeHandle handle = NativeMethods.git_index_get_bypath(index, path, stage); - - return handle.IsZero ? null : handle; + return NativeMethods.git_index_get_bypath(index, path, stage); } - public static bool git_index_has_conflicts(IndexSafeHandle index) + public static unsafe bool git_index_has_conflicts(IndexHandle index) { int res = NativeMethods.git_index_has_conflicts(index); Ensure.BooleanResult(res); @@ -904,238 +1116,226 @@ public static bool git_index_has_conflicts(IndexSafeHandle index) return res != 0; } - public static int git_index_name_entrycount(IndexSafeHandle index) + public static unsafe int git_index_name_entrycount(IndexHandle index) { - uint count = NativeMethods.git_index_name_entrycount(index); - if ((long)count > int.MaxValue) - { - throw new LibGit2SharpException("Index name entry count exceeds size of int"); - } - return (int)count; + return NativeMethods.git_index_name_entrycount(index) + .ConvertToInt(); } - public static IndexNameEntrySafeHandle git_index_name_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_name_entry* git_index_name_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_name_get_byindex(index, n); } - public static IndexSafeHandle git_index_open(FilePath indexpath) + public static unsafe IndexHandle git_index_open(FilePath indexpath) { - using (ThreadAffinity()) - { - IndexSafeHandle handle; - int res = NativeMethods.git_index_open(out handle, indexpath); - Ensure.ZeroResult(res); + git_index* handle; + int res = NativeMethods.git_index_open(out handle, indexpath); + Ensure.ZeroResult(res); - return handle; - } + return new IndexHandle(handle, true); } - public static void git_index_read(IndexSafeHandle index) + public static unsafe void git_index_read(IndexHandle index) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_read(index, false); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_read(index, false); + Ensure.ZeroResult(res); } - public static void git_index_remove_bypath(IndexSafeHandle index, FilePath path) + public static unsafe void git_index_remove_bypath(IndexHandle index, string path) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_remove_bypath(index, path); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_remove_bypath(index, path); + Ensure.ZeroResult(res); } - public static int git_index_reuc_entrycount(IndexSafeHandle index) + public static unsafe int git_index_reuc_entrycount(IndexHandle index) { - uint count = NativeMethods.git_index_reuc_entrycount(index); - if ((long)count > int.MaxValue) - { - throw new LibGit2SharpException("Index REUC entry count exceeds size of int"); - } - return (int)count; + return NativeMethods.git_index_reuc_entrycount(index) + .ConvertToInt(); } - public static IndexReucEntrySafeHandle git_index_reuc_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe git_index_reuc_entry* git_index_reuc_get_byindex(IndexHandle index, UIntPtr n) { return NativeMethods.git_index_reuc_get_byindex(index, n); } - public static IndexReucEntrySafeHandle git_index_reuc_get_bypath(IndexSafeHandle index, string path) + public static unsafe git_index_reuc_entry* git_index_reuc_get_bypath(IndexHandle index, string path) { return NativeMethods.git_index_reuc_get_bypath(index, path); } - public static void git_index_write(IndexSafeHandle index) + public static unsafe void git_index_write(IndexHandle index) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_write(index); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_write(index); + Ensure.ZeroResult(res); } - public static ObjectId git_tree_create_fromindex(Index index) + public static unsafe ObjectId git_index_write_tree(IndexHandle index) { - using (ThreadAffinity()) - { - GitOid treeOid; - int res = NativeMethods.git_index_write_tree(out treeOid, index.Handle); - Ensure.ZeroResult(res); + GitOid treeOid; + int res = NativeMethods.git_index_write_tree(out treeOid, index); + Ensure.ZeroResult(res); - return treeOid; - } + return treeOid; } - public static void git_index_read_fromtree(Index index, GitObjectSafeHandle tree) + public static unsafe ObjectId git_index_write_tree_to(IndexHandle index, RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_read_tree(index.Handle, tree); - Ensure.ZeroResult(res); - } + GitOid treeOid; + int res = NativeMethods.git_index_write_tree_to(out treeOid, index, repo); + Ensure.ZeroResult(res); + + return treeOid; } - public static void git_index_clear(Index index) + public static unsafe void git_index_read_fromtree(Index index, ObjectHandle tree) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_index_clear(index.Handle); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_index_read_tree(index.Handle, tree); + Ensure.ZeroResult(res); + } + + public static unsafe void git_index_clear(Index index) + { + int res = NativeMethods.git_index_clear(index.Handle); + Ensure.ZeroResult(res); } #endregion #region git_merge_ - public static ObjectId git_merge_base_many(RepositorySafeHandle repo, GitOid[] commitIds) + public static unsafe IndexHandle git_merge_commits(RepositoryHandle repo, ObjectHandle ourCommit, ObjectHandle theirCommit, GitMergeOpts opts, out bool earlyStop) { - using (ThreadAffinity()) + git_index* index; + int res = NativeMethods.git_merge_commits(out index, repo, ourCommit, theirCommit, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else { - GitOid ret; - int res = NativeMethods.git_merge_base_many(out ret, repo, commitIds.Length, commitIds); + earlyStop = false; + Ensure.ZeroResult(res); + } - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + return new IndexHandle(index, true); + } - Ensure.ZeroResult(res); + public static unsafe ObjectId git_merge_base_many(RepositoryHandle repo, GitOid[] commitIds) + { + GitOid ret; + int res = NativeMethods.git_merge_base_many(out ret, repo, commitIds.Length, commitIds); - return ret; + if (res == (int)GitErrorCode.NotFound) + { + return null; } + + Ensure.ZeroResult(res); + + return ret; } - public static ObjectId git_merge_base_octopus(RepositorySafeHandle repo, GitOid[] commitIds) + public static unsafe ObjectId git_merge_base_octopus(RepositoryHandle repo, GitOid[] commitIds) { - using (ThreadAffinity()) + GitOid ret; + int res = NativeMethods.git_merge_base_octopus(out ret, repo, commitIds.Length, commitIds); + + if (res == (int)GitErrorCode.NotFound) { - GitOid ret; - int res = NativeMethods.git_merge_base_octopus(out ret, repo, commitIds.Length, commitIds); + return null; + } - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + Ensure.ZeroResult(res); - Ensure.ZeroResult(res); + return ret; + } - return ret; - } + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_fetchhead(RepositoryHandle repo, string branchName, string remoteUrl, GitOid oid) + { + git_annotated_commit* commit; + + int res = NativeMethods.git_annotated_commit_from_fetchhead(out commit, repo, branchName, remoteUrl, ref oid); + + Ensure.ZeroResult(res); + + return new AnnotatedCommitHandle(commit, true); } - public static GitMergeHeadHandle git_merge_head_from_fetchhead(RepositorySafeHandle repo, string branchName, string remoteUrl, GitOid oid) + public static unsafe AnnotatedCommitHandle git_annotated_commit_lookup(RepositoryHandle repo, GitOid oid) { - using (ThreadAffinity()) - { - GitMergeHeadHandle merge_head; + git_annotated_commit* commit; - int res = NativeMethods.git_merge_head_from_fetchhead(out merge_head, repo, branchName, remoteUrl, ref oid); + int res = NativeMethods.git_annotated_commit_lookup(out commit, repo, ref oid); - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return merge_head; - } + return new AnnotatedCommitHandle(commit, true); } - public static GitMergeHeadHandle git_merge_head_from_id(RepositorySafeHandle repo, GitOid oid) + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_ref(RepositoryHandle repo, ReferenceHandle reference) { - using (ThreadAffinity()) - { - GitMergeHeadHandle their_head; + git_annotated_commit* commit; - int res = NativeMethods.git_merge_head_from_id(out their_head, repo, ref oid); + int res = NativeMethods.git_annotated_commit_from_ref(out commit, repo, reference); - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return their_head; - } + return new AnnotatedCommitHandle(commit, true); } - public static GitMergeHeadHandle git_merge_head_from_ref(RepositorySafeHandle repo, ReferenceSafeHandle reference) + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_revspec(RepositoryHandle repo, string revspec) { - using (ThreadAffinity()) - { - GitMergeHeadHandle their_head; + git_annotated_commit* commit; - int res = NativeMethods.git_merge_head_from_ref(out their_head, repo, reference); + int res = NativeMethods.git_annotated_commit_from_revspec(out commit, repo, revspec); - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return their_head; - } + return new AnnotatedCommitHandle(commit, true); } - public static ObjectId git_merge_head_id(GitMergeHeadHandle mergeHead) + public static unsafe ObjectId git_annotated_commit_id(AnnotatedCommitHandle mergeHead) { - return NativeMethods.git_merge_head_id(mergeHead).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_annotated_commit_id(mergeHead)); } - public static void git_merge(RepositorySafeHandle repo, GitMergeHeadHandle[] heads, GitMergeOpts mergeOptions, GitCheckoutOpts checkoutOptions) + public static unsafe void git_merge(RepositoryHandle repo, AnnotatedCommitHandle[] heads, GitMergeOpts mergeOptions, GitCheckoutOpts checkoutOptions, out bool earlyStop) { - using (ThreadAffinity()) - { - IntPtr[] their_heads = heads.Select(head => head.DangerousGetHandle()).ToArray(); + IntPtr[] their_heads = heads.Select(head => head.AsIntPtr()).ToArray(); - int res = NativeMethods.git_merge( - repo, - their_heads, - (UIntPtr)their_heads.Length, - ref mergeOptions, - ref checkoutOptions); + int res = NativeMethods.git_merge(repo, + their_heads, + (UIntPtr)their_heads.Length, + ref mergeOptions, + ref checkoutOptions); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; Ensure.ZeroResult(res); } } - public static void git_merge_analysis( - RepositorySafeHandle repo, - GitMergeHeadHandle[] heads, + public static unsafe void git_merge_analysis( + RepositoryHandle repo, + AnnotatedCommitHandle[] heads, out GitMergeAnalysis analysis_out, out GitMergePreference preference_out) { - using (ThreadAffinity()) - { - IntPtr[] their_heads = heads.Select(head => head.DangerousGetHandle()).ToArray(); + IntPtr[] their_heads = heads.Select(head => head.AsIntPtr()).ToArray(); - int res = NativeMethods.git_merge_analysis( - out analysis_out, - out preference_out, - repo, - their_heads, - their_heads.Length); - - Ensure.ZeroResult(res); - } - } + int res = NativeMethods.git_merge_analysis(out analysis_out, + out preference_out, + repo, + their_heads, + their_heads.Length); - public static void git_merge_head_free(IntPtr handle) - { - NativeMethods.git_merge_head_free(handle); + Ensure.ZeroResult(res); } #endregion @@ -1155,10 +1355,9 @@ public static string git_message_prettify(string message, char? commentChar) throw new InvalidOperationException("Only single byte characters are allowed as commentary characters in a message (eg. '#')."); } - using (ThreadAffinity()) using (var buf = new GitBuf()) { - int res = NativeMethods.git_message_prettify(buf, message, false, (sbyte)comment); + int res = NativeMethods.git_message_prettify(buf, message, true, (sbyte)comment); Ensure.Int32Result(res); return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; @@ -1169,87 +1368,78 @@ public static string git_message_prettify(string message, char? commentChar) #region git_note_ - public static ObjectId git_note_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_note_create( + RepositoryHandle repo, + string notes_ref, Signature author, Signature committer, - string notes_ref, ObjectId targetId, string note, bool force) { - using (ThreadAffinity()) - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) { GitOid noteOid; GitOid oid = targetId.Oid; - int res = NativeMethods.git_note_create(out noteOid, repo, authorHandle, committerHandle, notes_ref, ref oid, note, force ? 1 : 0); + int res = NativeMethods.git_note_create(out noteOid, repo, notes_ref, authorHandle, committerHandle, ref oid, note, force ? 1 : 0); Ensure.ZeroResult(res); return noteOid; } } - public static string git_note_default_ref(RepositorySafeHandle repo) + public static unsafe string git_note_default_ref(RepositoryHandle repo) { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - string notes_ref; - int res = NativeMethods.git_note_default_ref(out notes_ref, repo); + int res = NativeMethods.git_note_default_ref(buf, repo); Ensure.ZeroResult(res); - return notes_ref; + return LaxUtf8Marshaler.FromNative(buf.ptr); } } - public static ICollection git_note_foreach(RepositorySafeHandle repo, string notes_ref, Func resultSelector) + public static unsafe ICollection git_note_foreach(RepositoryHandle repo, string notes_ref, Func resultSelector) { - return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, notes_ref, - (ref GitOid x, ref GitOid y, IntPtr p) => c(x, y, p), IntPtr.Zero)); + return git_foreach(resultSelector, c => NativeMethods.git_note_foreach(repo, + notes_ref, + (ref GitOid x, ref GitOid y, IntPtr p) => c(x, y, p), + IntPtr.Zero), GitErrorCode.NotFound); } - public static void git_note_free(IntPtr note) - { - NativeMethods.git_note_free(note); - } - - public static string git_note_message(NoteSafeHandle note) + public static unsafe string git_note_message(NoteHandle note) { return NativeMethods.git_note_message(note); } - public static ObjectId git_note_id(NoteSafeHandle note) + public static unsafe ObjectId git_note_id(NoteHandle note) { - return NativeMethods.git_note_id(note).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_note_id(note)); } - public static NoteSafeHandle git_note_read(RepositorySafeHandle repo, string notes_ref, ObjectId id) + public static unsafe NoteHandle git_note_read(RepositoryHandle repo, string notes_ref, ObjectId id) { - using (ThreadAffinity()) - { - GitOid oid = id.Oid; - NoteSafeHandle note; + GitOid oid = id.Oid; + git_note* note; - int res = NativeMethods.git_note_read(out note, repo, notes_ref, ref oid); + int res = NativeMethods.git_note_read(out note, repo, notes_ref, ref oid); - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + if (res == (int)GitErrorCode.NotFound) + { + return null; + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return note; - } + return new NoteHandle(note, true); } - public static void git_note_remove(RepositorySafeHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) + public static unsafe void git_note_remove(RepositoryHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) { - using (ThreadAffinity()) - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) { GitOid oid = targetId.Oid; @@ -1268,64 +1458,53 @@ public static void git_note_remove(RepositorySafeHandle repo, string notes_ref, #region git_object_ - public static ObjectId git_object_id(GitObjectSafeHandle obj) + public static unsafe ObjectId git_object_id(ObjectHandle obj) { - return NativeMethods.git_object_id(obj).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_object_id(obj)); } - public static void git_object_free(IntPtr obj) + public static unsafe ObjectHandle git_object_lookup(RepositoryHandle repo, ObjectId id, GitObjectType type) { - NativeMethods.git_object_free(obj); - } + git_object* handle; + GitOid oid = id.Oid; - public static GitObjectSafeHandle git_object_lookup(RepositorySafeHandle repo, ObjectId id, GitObjectType type) - { - using (ThreadAffinity()) + int res = NativeMethods.git_object_lookup(out handle, repo, ref oid, type); + switch (res) { - GitObjectSafeHandle handle; - GitOid oid = id.Oid; - - int res = NativeMethods.git_object_lookup(out handle, repo, ref oid, type); - switch (res) - { - case (int)GitErrorCode.NotFound: - return null; - - default: - Ensure.ZeroResult(res); - break; - } + case (int)GitErrorCode.NotFound: + return null; - return handle; + default: + Ensure.ZeroResult(res); + break; } + + return new ObjectHandle(handle, true); } - public static GitObjectSafeHandle git_object_peel(RepositorySafeHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) + public static unsafe ObjectHandle git_object_peel(RepositoryHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) { - using (ThreadAffinity()) - { - GitObjectSafeHandle peeled; - int res; - - using (var obj = new ObjectSafeWrapper(id, repo)) - { - res = NativeMethods.git_object_peel(out peeled, obj.ObjectPtr, type); - } + git_object* peeled; + int res; - if (!throwsIfCanNotPeel && - (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous)) - { - return null; - } + using (var obj = new ObjectSafeWrapper(id, repo)) + { + res = NativeMethods.git_object_peel(out peeled, obj.ObjectPtr, type); + } - Ensure.ZeroResult(res); - return peeled; + if (!throwsIfCanNotPeel && + (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous || + res == (int)GitErrorCode.InvalidSpecification || res == (int)GitErrorCode.Peel)) + { + return null; } + + Ensure.ZeroResult(res); + return new ObjectHandle(peeled, true); } - public static string git_object_short_id(RepositorySafeHandle repo, ObjectId id) + public static unsafe string git_object_short_id(RepositoryHandle repo, ObjectId id) { - using (ThreadAffinity()) using (var obj = new ObjectSafeWrapper(id, repo)) using (var buf = new GitBuf()) { @@ -1336,7 +1515,7 @@ public static string git_object_short_id(RepositorySafeHandle repo, ObjectId id) } } - public static GitObjectType git_object_type(GitObjectSafeHandle obj) + public static unsafe GitObjectType git_object_type(ObjectHandle obj) { return NativeMethods.git_object_type(obj); } @@ -1345,7 +1524,7 @@ public static GitObjectType git_object_type(GitObjectSafeHandle obj) #region git_odb_ - public static void git_odb_add_backend(ObjectDatabaseSafeHandle odb, IntPtr backend, int priority) + public static unsafe void git_odb_add_backend(ObjectDatabaseHandle odb, IntPtr backend, int priority) { Ensure.ZeroResult(NativeMethods.git_odb_add_backend(odb, backend, priority)); } @@ -1356,16 +1535,16 @@ public static IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len) if (IntPtr.Zero == toReturn) { - throw new LibGit2SharpException(String.Format(CultureInfo.InvariantCulture, - "Unable to allocate {0} bytes; out of memory", - len), - GitErrorCode.Error, GitErrorCategory.NoMemory); + throw new LibGit2SharpException("Unable to allocate {0} bytes; out of memory", + len, + GitErrorCode.Error, + GitErrorCategory.NoMemory); } return toReturn; } - public static bool git_odb_exists(ObjectDatabaseSafeHandle odb, ObjectId id) + public static unsafe bool git_odb_exists(ObjectDatabaseHandle odb, ObjectId id) { GitOid oid = id.Oid; @@ -1375,190 +1554,321 @@ public static bool git_odb_exists(ObjectDatabaseSafeHandle odb, ObjectId id) return (res == 1); } - public static ICollection git_odb_foreach( - ObjectDatabaseSafeHandle odb, - Func resultSelector) + public static unsafe GitObjectMetadata git_odb_read_header(ObjectDatabaseHandle odb, ObjectId id) { - return git_foreach( - resultSelector, - c => NativeMethods.git_odb_foreach( - odb, - (x, p) => c(x, p), - IntPtr.Zero)); - } + GitOid oid = id.Oid; + UIntPtr length; + GitObjectType objectType; - public static OdbStreamSafeHandle git_odb_open_wstream(ObjectDatabaseSafeHandle odb, UIntPtr size, GitObjectType type) - { - using (ThreadAffinity()) - { - OdbStreamSafeHandle stream; - int res = NativeMethods.git_odb_open_wstream(out stream, odb, size, type); - Ensure.ZeroResult(res); + int res = NativeMethods.git_odb_read_header(out length, out objectType, odb, ref oid); + Ensure.ZeroResult(res); - return stream; - } + return new GitObjectMetadata((long)length, objectType); } - public static void git_odb_free(IntPtr odb) + public static unsafe ICollection git_odb_foreach(ObjectDatabaseHandle odb) { - NativeMethods.git_odb_free(odb); + var list = new List(); + + NativeMethods.git_odb_foreach(odb, (p, _data) => + { + list.Add(ObjectId.BuildFromPtr(p)); + return 0; + }, IntPtr.Zero); + + return list; + } + + public static unsafe OdbStreamHandle git_odb_open_wstream(ObjectDatabaseHandle odb, long size, GitObjectType type) + { + git_odb_stream* stream; + int res = NativeMethods.git_odb_open_wstream(out stream, odb, size, type); + Ensure.ZeroResult(res); + + return new OdbStreamHandle(stream, true); } - public static void git_odb_stream_write(OdbStreamSafeHandle stream, byte[] data, int len) + public static void git_odb_stream_write(OdbStreamHandle stream, byte[] data, int len) { - using (ThreadAffinity()) + int res; + unsafe { - int res; - unsafe + fixed (byte* p = data) { - fixed (byte *p = data) - { - res = NativeMethods.git_odb_stream_write(stream, (IntPtr) p, (UIntPtr) len); - } + res = NativeMethods.git_odb_stream_write(stream, (IntPtr)p, (UIntPtr)len); } - - Ensure.ZeroResult(res); } + + Ensure.ZeroResult(res); } - public static ObjectId git_odb_stream_finalize_write(OdbStreamSafeHandle stream) + public static unsafe ObjectId git_odb_stream_finalize_write(OdbStreamHandle stream) { - using (ThreadAffinity()) - { - GitOid id; - int res = NativeMethods.git_odb_stream_finalize_write(out id, stream); - Ensure.ZeroResult(res); + GitOid id; + int res = NativeMethods.git_odb_stream_finalize_write(out id, stream); + Ensure.ZeroResult(res); - return id; - } + return id; } - public static void git_odb_stream_free(IntPtr stream) + public static unsafe ObjectId git_odb_write(ObjectDatabaseHandle odb, byte[] data, ObjectType type) { - NativeMethods.git_odb_stream_free(stream); + GitOid id; + int res; + fixed (byte* p = data) + { + res = NativeMethods.git_odb_write(out id, odb, p, new UIntPtr((ulong)data.LongLength), type.ToGitObjectType()); + } + Ensure.ZeroResult(res); + + return id; } #endregion #region git_patch_ - public static void git_patch_free(IntPtr patch) + public static unsafe PatchHandle git_patch_from_diff(DiffHandle diff, int idx) { - NativeMethods.git_patch_free(patch); + git_patch* handle; + int res = NativeMethods.git_patch_from_diff(out handle, diff, (UIntPtr)idx); + Ensure.ZeroResult(res); + return new PatchHandle(handle, true); } - public static PatchSafeHandle git_patch_from_diff(DiffSafeHandle diff, int idx) + public static unsafe void git_patch_print(PatchHandle patch, NativeMethods.git_diff_line_cb printCallback) { - using (ThreadAffinity()) - { - PatchSafeHandle handle; - int res = NativeMethods.git_patch_from_diff(out handle, diff, (UIntPtr) idx); - Ensure.ZeroResult(res); - return handle; - } + int res = NativeMethods.git_patch_print(patch, printCallback, IntPtr.Zero); + Ensure.ZeroResult(res); } - public static void git_patch_print(PatchSafeHandle patch, NativeMethods.git_diff_line_cb printCallback) + public static unsafe Tuple git_patch_line_stats(PatchHandle patch) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_patch_print(patch, printCallback, IntPtr.Zero); - Ensure.ZeroResult(res); - } + UIntPtr ctx, add, del; + int res = NativeMethods.git_patch_line_stats(out ctx, out add, out del, patch); + Ensure.ZeroResult(res); + return new Tuple((int)add, (int)del); } - public static Tuple git_patch_line_stats(PatchSafeHandle patch) + #endregion + + #region git_packbuilder_ + + public static unsafe PackBuilderHandle git_packbuilder_new(RepositoryHandle repo) { - using (ThreadAffinity()) - { - UIntPtr ctx, add, del; - int res = NativeMethods.git_patch_line_stats(out ctx, out add, out del, patch); - Ensure.ZeroResult(res); - return new Tuple((int)add, (int)del); - } + git_packbuilder* handle; + + int res = NativeMethods.git_packbuilder_new(out handle, repo); + Ensure.ZeroResult(res); + + return new PackBuilderHandle(handle, true); + } + + public static unsafe void git_packbuilder_insert(PackBuilderHandle packbuilder, ObjectId targetId, string name) + { + GitOid oid = targetId.Oid; + + int res = NativeMethods.git_packbuilder_insert(packbuilder, ref oid, name); + Ensure.ZeroResult(res); + } + + internal static unsafe void git_packbuilder_insert_commit(PackBuilderHandle packbuilder, ObjectId targetId) + { + GitOid oid = targetId.Oid; + + int res = NativeMethods.git_packbuilder_insert_commit(packbuilder, ref oid); + Ensure.ZeroResult(res); + } + + internal static unsafe void git_packbuilder_insert_tree(PackBuilderHandle packbuilder, ObjectId targetId) + { + GitOid oid = targetId.Oid; + + int res = NativeMethods.git_packbuilder_insert_tree(packbuilder, ref oid); + Ensure.ZeroResult(res); + } + + public static unsafe void git_packbuilder_insert_recur(PackBuilderHandle packbuilder, ObjectId targetId, string name) + { + GitOid oid = targetId.Oid; + + int res = NativeMethods.git_packbuilder_insert_recur(packbuilder, ref oid, name); + Ensure.ZeroResult(res); + } + + public static unsafe uint git_packbuilder_set_threads(PackBuilderHandle packbuilder, uint numThreads) + { + return NativeMethods.git_packbuilder_set_threads(packbuilder, numThreads); + } + + public static unsafe void git_packbuilder_write(PackBuilderHandle packbuilder, FilePath path) + { + int res = NativeMethods.git_packbuilder_write(packbuilder, path, 0, IntPtr.Zero, IntPtr.Zero); + Ensure.ZeroResult(res); + } + + public static unsafe UIntPtr git_packbuilder_object_count(PackBuilderHandle packbuilder) + { + return NativeMethods.git_packbuilder_object_count(packbuilder); } + public static unsafe UIntPtr git_packbuilder_written(PackBuilderHandle packbuilder) + { + return NativeMethods.git_packbuilder_written(packbuilder); + } #endregion - #region git_push_ + #region git_rebase - public static void git_push_add_refspec(PushSafeHandle push, string pushRefSpec) + public static unsafe RebaseHandle git_rebase_init( + RepositoryHandle repo, + AnnotatedCommitHandle branch, + AnnotatedCommitHandle upstream, + AnnotatedCommitHandle onto, + GitRebaseOptions options) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_add_refspec(push, pushRefSpec); - Ensure.ZeroResult(res); - } + git_rebase* rebase = null; + + int result = NativeMethods.git_rebase_init(out rebase, repo, branch, upstream, onto, options); + Ensure.ZeroResult(result); + + return new RebaseHandle(rebase, true); } - public static void git_push_finish(PushSafeHandle push) + public static unsafe RebaseHandle git_rebase_open(RepositoryHandle repo, GitRebaseOptions options) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_finish(push); - Ensure.ZeroResult(res); - } + git_rebase* rebase = null; + + int result = NativeMethods.git_rebase_open(out rebase, repo, options); + Ensure.ZeroResult(result); + + return new RebaseHandle(rebase, true); } - public static void git_push_free(IntPtr push) + public static unsafe long git_rebase_operation_entrycount(RebaseHandle rebase) { - NativeMethods.git_push_free(push); + return NativeMethods.git_rebase_operation_entrycount(rebase).ConvertToLong(); } - public static PushSafeHandle git_push_new(RemoteSafeHandle remote) + public static unsafe long git_rebase_operation_current(RebaseHandle rebase) { - using (ThreadAffinity()) + UIntPtr result = NativeMethods.git_rebase_operation_current(rebase); + + if (result == GIT_REBASE_NO_OPERATION) { - PushSafeHandle handle; - int res = NativeMethods.git_push_new(out handle, remote); - Ensure.ZeroResult(res); - return handle; + return RebaseNoOperation; + } + else + { + return result.ConvertToLong(); } } - public static void git_push_set_callbacks( - PushSafeHandle push, - NativeMethods.git_push_transfer_progress pushTransferProgress, - NativeMethods.git_packbuilder_progress packBuilderProgress) + /// + /// The value from the native layer indicating that no rebase operation is in progress. + /// + private static UIntPtr GIT_REBASE_NO_OPERATION { - using (ThreadAffinity()) + get { - int res = NativeMethods.git_push_set_callbacks(push, packBuilderProgress, IntPtr.Zero, pushTransferProgress, IntPtr.Zero); - Ensure.ZeroResult(res); + return UIntPtr.Size == 4 ? new UIntPtr(uint.MaxValue) : new UIntPtr(ulong.MaxValue); } } - public static void git_push_set_options(PushSafeHandle push, GitPushOptions options) + public const long RebaseNoOperation = -1; + + public static unsafe git_rebase_operation* git_rebase_operation_byindex( + RebaseHandle rebase, + long index) + { + Debug.Assert(index >= 0); + return NativeMethods.git_rebase_operation_byindex(rebase, ((UIntPtr)index)); + } + + /// + /// Returns null when finished. + /// + /// + /// + public static unsafe git_rebase_operation* git_rebase_next(RebaseHandle rebase) { - using (ThreadAffinity()) + git_rebase_operation* ptr; + int result = NativeMethods.git_rebase_next(out ptr, rebase); + if (result == (int)GitErrorCode.IterOver) { - int res = NativeMethods.git_push_set_options(push, options); - Ensure.ZeroResult(res); + return null; } + Ensure.ZeroResult(result); + + return ptr; } - public static void git_push_status_foreach(PushSafeHandle push, NativeMethods.push_status_foreach_cb status_cb) + public static unsafe GitRebaseCommitResult git_rebase_commit( + RebaseHandle rebase, + Identity author, + Identity committer) { - using (ThreadAffinity()) + Ensure.ArgumentNotNull(rebase, "rebase"); + Ensure.ArgumentNotNull(committer, "committer"); + + using (SignatureHandle committerHandle = committer.BuildNowSignatureHandle()) + using (SignatureHandle authorHandle = author.SafeBuildNowSignatureHandle()) { - int res = NativeMethods.git_push_status_foreach(push, status_cb, IntPtr.Zero); - Ensure.ZeroResult(res); + GitRebaseCommitResult commitResult = new GitRebaseCommitResult(); + + int result = NativeMethods.git_rebase_commit(ref commitResult.CommitId, rebase, authorHandle, committerHandle, IntPtr.Zero, IntPtr.Zero); + + if (result == (int)GitErrorCode.Applied) + { + commitResult.CommitId = GitOid.Empty; + commitResult.WasPatchAlreadyApplied = true; + } + else + { + Ensure.ZeroResult(result); + } + + return commitResult; } } - public static bool git_push_unpack_ok(PushSafeHandle push) + /// + /// Struct to report the result of calling git_rebase_commit. + /// + public struct GitRebaseCommitResult + { + /// + /// The ID of the commit that was generated, if any + /// + public GitOid CommitId; + + /// + /// bool to indicate if the patch was already applied. + /// If Patch was already applied, then CommitId will be empty (all zeros). + /// + public bool WasPatchAlreadyApplied; + } + + public static unsafe void git_rebase_abort( + RebaseHandle rebase) { - int res = NativeMethods.git_push_unpack_ok(push); - return res == 1; + Ensure.ArgumentNotNull(rebase, "rebase"); + + int result = NativeMethods.git_rebase_abort(rebase); + Ensure.ZeroResult(result); } - public static void git_push_update_tips(PushSafeHandle push, Signature signature, string logMessage) + public static unsafe void git_rebase_finish( + RebaseHandle rebase, + Identity committer) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) + Ensure.ArgumentNotNull(rebase, "rebase"); + Ensure.ArgumentNotNull(committer, "committer"); + + using (var signatureHandle = committer.BuildNowSignatureHandle()) { - int res = NativeMethods.git_push_update_tips(push, sigHandle, logMessage); - Ensure.ZeroResult(res); + int result = NativeMethods.git_rebase_finish(rebase, signatureHandle); + Ensure.ZeroResult(result); } } @@ -1566,49 +1876,45 @@ public static void git_push_update_tips(PushSafeHandle push, Signature signature #region git_reference_ - public static ReferenceSafeHandle git_reference_create(RepositorySafeHandle repo, string name, ObjectId targetId, bool allowOverwrite, - Signature signature, string logMessage) + public static unsafe ReferenceHandle git_reference_create( + RepositoryHandle repo, + string name, + ObjectId targetId, + bool allowOverwrite, + string logMessage) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - GitOid oid = targetId.Oid; - ReferenceSafeHandle handle; + GitOid oid = targetId.Oid; + git_reference* handle; - int res = NativeMethods.git_reference_create(out handle, repo, name, ref oid, allowOverwrite, sigHandle, logMessage); - Ensure.ZeroResult(res); + int res = NativeMethods.git_reference_create(out handle, repo, name, ref oid, allowOverwrite, logMessage); + Ensure.ZeroResult(res); - return handle; - } + return new ReferenceHandle(handle, true); } - public static ReferenceSafeHandle git_reference_symbolic_create(RepositorySafeHandle repo, string name, string target, bool allowOverwrite, - Signature signature, string logMessage) + public static unsafe ReferenceHandle git_reference_symbolic_create( + RepositoryHandle repo, + string name, + string target, + bool allowOverwrite, + string logMessage) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_symbolic_create(out handle, repo, name, target, allowOverwrite, sigHandle, logMessage); - Ensure.ZeroResult(res); + git_reference* handle; + int res = NativeMethods.git_reference_symbolic_create(out handle, repo, name, target, allowOverwrite, + logMessage); + Ensure.ZeroResult(res); - return handle; - } + return new ReferenceHandle(handle, true); } - public static ICollection git_reference_foreach_glob( - RepositorySafeHandle repo, + public static unsafe ICollection git_reference_foreach_glob( + RepositoryHandle repo, string glob, Func resultSelector) { return git_foreach(resultSelector, c => NativeMethods.git_reference_foreach_glob(repo, glob, (x, p) => c(x, p), IntPtr.Zero)); } - public static void git_reference_free(IntPtr reference) - { - NativeMethods.git_reference_free(reference); - } - public static bool git_reference_is_valid_name(string refname) { int res = NativeMethods.git_reference_is_valid_name(refname); @@ -1617,174 +1923,145 @@ public static bool git_reference_is_valid_name(string refname) return (res == 1); } - public static IList git_reference_list(RepositorySafeHandle repo) + public static unsafe IList git_reference_list(RepositoryHandle repo) { - using (ThreadAffinity()) - { - var array = new GitStrArrayNative(); + var array = new GitStrArrayNative(); - try - { - int res = NativeMethods.git_reference_list(out array.Array, repo); - Ensure.ZeroResult(res); + try + { + int res = NativeMethods.git_reference_list(out array.Array, repo); + Ensure.ZeroResult(res); - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static ReferenceSafeHandle git_reference_lookup(RepositorySafeHandle repo, string name, bool shouldThrowIfNotFound) + public static unsafe ReferenceHandle git_reference_lookup(RepositoryHandle repo, string name, bool shouldThrowIfNotFound) { - using (ThreadAffinity()) - { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_lookup(out handle, repo, name); + git_reference* handle; + int res = NativeMethods.git_reference_lookup(out handle, repo, name); - if (!shouldThrowIfNotFound && res == (int)GitErrorCode.NotFound) - { - return null; - } + if (!shouldThrowIfNotFound && res == (int)GitErrorCode.NotFound) + { + return null; + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return handle; - } + return new ReferenceHandle(handle, true); } - public static string git_reference_name(ReferenceSafeHandle reference) + public static unsafe string git_reference_name(git_reference* reference) { return NativeMethods.git_reference_name(reference); } - public static void git_reference_remove(RepositorySafeHandle repo, string name) + public static unsafe void git_reference_remove(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_reference_remove(repo, name); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_reference_remove(repo, name); + Ensure.ZeroResult(res); } - public static ObjectId git_reference_target(ReferenceSafeHandle reference) + public static unsafe ObjectId git_reference_target(git_reference* reference) { - return NativeMethods.git_reference_target(reference).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_reference_target(reference)); } - public static ReferenceSafeHandle git_reference_rename(ReferenceSafeHandle reference, string newName, bool allowOverwrite, - Signature signature, string logMessage) + public static unsafe ReferenceHandle git_reference_rename( + ReferenceHandle reference, + string newName, + bool allowOverwrite, + string logMessage) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - ReferenceSafeHandle ref_out; + git_reference* ref_out; - int res = NativeMethods.git_reference_rename(out ref_out, reference, newName, allowOverwrite, sigHandle, logMessage); - Ensure.ZeroResult(res); + int res = NativeMethods.git_reference_rename(out ref_out, reference, newName, allowOverwrite, logMessage); + Ensure.ZeroResult(res); - return ref_out; - } + return new ReferenceHandle(ref_out, true); } - public static ReferenceSafeHandle git_reference_set_target(ReferenceSafeHandle reference, ObjectId id, Signature signature, string logMessage) + public static unsafe ReferenceHandle git_reference_set_target(ReferenceHandle reference, ObjectId id, string logMessage) { - using (ThreadAffinity()) - using (SignatureSafeHandle sigHandle = signature.BuildHandle()) - { - GitOid oid = id.Oid; - ReferenceSafeHandle ref_out; + GitOid oid = id.Oid; + git_reference* ref_out; - int res = NativeMethods.git_reference_set_target(out ref_out, reference, ref oid, sigHandle, logMessage); - Ensure.ZeroResult(res); + int res = NativeMethods.git_reference_set_target(out ref_out, reference, ref oid, logMessage); + Ensure.ZeroResult(res); - return ref_out; - } + return new ReferenceHandle(ref_out, true); } - public static ReferenceSafeHandle git_reference_symbolic_set_target(ReferenceSafeHandle reference, string target, Signature signature, string logMessage) + public static unsafe ReferenceHandle git_reference_symbolic_set_target(ReferenceHandle reference, string target, string logMessage) { - using (ThreadAffinity()) - using (SignatureSafeHandle sigHandle = signature.BuildHandle()) - { - ReferenceSafeHandle ref_out; + git_reference* ref_out; - int res = NativeMethods.git_reference_symbolic_set_target(out ref_out, reference, target, sigHandle, logMessage); - Ensure.ZeroResult(res); + int res = NativeMethods.git_reference_symbolic_set_target(out ref_out, reference, target, logMessage); + Ensure.ZeroResult(res); - return ref_out; - } + return new ReferenceHandle(ref_out, true); } - public static string git_reference_symbolic_target(ReferenceSafeHandle reference) + public static unsafe string git_reference_symbolic_target(git_reference* reference) { return NativeMethods.git_reference_symbolic_target(reference); } - public static GitReferenceType git_reference_type(ReferenceSafeHandle reference) + public static unsafe GitReferenceType git_reference_type(git_reference* reference) { return NativeMethods.git_reference_type(reference); } - public static void git_reference_ensure_log(RepositorySafeHandle repo, string refname) + public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string refname) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_reference_ensure_log(repo, refname); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_reference_ensure_log(repo, refname); + Ensure.ZeroResult(res); } #endregion #region git_reflog_ - public static void git_reflog_free(IntPtr reflog) - { - NativeMethods.git_reflog_free(reflog); - } - - public static ReflogSafeHandle git_reflog_read(RepositorySafeHandle repo, string canonicalName) + public static unsafe ReflogHandle git_reflog_read(RepositoryHandle repo, string canonicalName) { - using (ThreadAffinity()) - { - ReflogSafeHandle reflog_out; + git_reflog* reflog_out; - int res = NativeMethods.git_reflog_read(out reflog_out, repo, canonicalName); - Ensure.ZeroResult(res); + int res = NativeMethods.git_reflog_read(out reflog_out, repo, canonicalName); + Ensure.ZeroResult(res); - return reflog_out; - } + return new ReflogHandle(reflog_out, true); } - public static int git_reflog_entrycount(ReflogSafeHandle reflog) + public static unsafe int git_reflog_entrycount(ReflogHandle reflog) { return (int)NativeMethods.git_reflog_entrycount(reflog); } - public static ReflogEntrySafeHandle git_reflog_entry_byindex(ReflogSafeHandle reflog, int idx) + public static unsafe git_reflog_entry* git_reflog_entry_byindex(ReflogHandle reflog, int idx) { return NativeMethods.git_reflog_entry_byindex(reflog, (UIntPtr)idx); } - public static ObjectId git_reflog_entry_id_old(SafeHandle entry) + public static unsafe ObjectId git_reflog_entry_id_old(git_reflog_entry* entry) { - return NativeMethods.git_reflog_entry_id_old(entry).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_old(entry)); } - public static ObjectId git_reflog_entry_id_new(SafeHandle entry) + public static unsafe ObjectId git_reflog_entry_id_new(git_reflog_entry* entry) { - return NativeMethods.git_reflog_entry_id_new(entry).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_new(entry)); } - public static Signature git_reflog_entry_committer(SafeHandle entry) + public static unsafe Signature git_reflog_entry_committer(git_reflog_entry* entry) { return new Signature(NativeMethods.git_reflog_entry_committer(entry)); } - public static string git_reflog_entry_message(SafeHandle entry) + public static unsafe string git_reflog_entry_message(git_reflog_entry* entry) { return NativeMethods.git_reflog_entry_message(entry); } @@ -1793,9 +2070,19 @@ public static string git_reflog_entry_message(SafeHandle entry) #region git_refspec - public static string git_refspec_rtransform(GitRefSpecHandle refSpecPtr, string name) + public static unsafe string git_refspec_transform(IntPtr refSpecPtr, string name) + { + using (var buf = new GitBuf()) + { + int res = NativeMethods.git_refspec_transform(buf, refSpecPtr, name); + Ensure.ZeroResult(res); + + return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + } + + public static unsafe string git_refspec_rtransform(IntPtr refSpecPtr, string name) { - using (ThreadAffinity()) using (var buf = new GitBuf()) { int res = NativeMethods.git_refspec_rtransform(buf, refSpecPtr, name); @@ -1805,229 +2092,207 @@ public static string git_refspec_rtransform(GitRefSpecHandle refSpecPtr, string } } - public static string git_refspec_string(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_string(IntPtr refspec) { - return NativeMethods.git_refspec_string(refSpec); + return NativeMethods.git_refspec_string(refspec); } - public static string git_refspec_src(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_src(IntPtr refSpec) { return NativeMethods.git_refspec_src(refSpec); } - public static string git_refspec_dst(GitRefSpecHandle refSpec) + public static unsafe string git_refspec_dst(IntPtr refSpec) { return NativeMethods.git_refspec_dst(refSpec); } - public static RefSpecDirection git_refspec_direction(GitRefSpecHandle refSpec) + public static unsafe RefSpecDirection git_refspec_direction(IntPtr refSpec) { return NativeMethods.git_refspec_direction(refSpec); } - public static bool git_refspec_force(GitRefSpecHandle refSpec) + public static unsafe bool git_refspec_force(IntPtr refSpec) { return NativeMethods.git_refspec_force(refSpec); } + public static bool git_refspec_src_matches(IntPtr refspec, string reference) + { + return NativeMethods.git_refspec_src_matches(refspec, reference); + } + + public static bool git_refspec_dst_matches(IntPtr refspec, string reference) + { + return NativeMethods.git_refspec_dst_matches(refspec, reference); + } + #endregion #region git_remote_ - public static TagFetchMode git_remote_autotag(RemoteSafeHandle remote) + public static unsafe TagFetchMode git_remote_autotag(RemoteHandle remote) { - return (TagFetchMode) NativeMethods.git_remote_autotag(remote); + return (TagFetchMode)NativeMethods.git_remote_autotag(remote); } - public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, string name, string url) + public static unsafe RemoteHandle git_remote_create(RepositoryHandle repo, string name, string url) { - using (ThreadAffinity()) - { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_create(out handle, repo, name, url); - Ensure.ZeroResult(res); + git_remote* handle; + int res = NativeMethods.git_remote_create(out handle, repo, name, url); + Ensure.ZeroResult(res); - return handle; - } + return new RemoteHandle(handle, true); } - public static RemoteSafeHandle git_remote_create_with_fetchspec(RepositorySafeHandle repo, string name, string url, string refspec) + public static unsafe RemoteHandle git_remote_create_with_fetchspec(RepositoryHandle repo, string name, string url, string refspec) { - using (ThreadAffinity()) - { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_create_with_fetchspec(out handle, repo, name, url, refspec); - Ensure.ZeroResult(res); + git_remote* handle; + int res = NativeMethods.git_remote_create_with_fetchspec(out handle, repo, name, url, refspec); + Ensure.ZeroResult(res); - return handle; - } + return new RemoteHandle(handle, true); } - public static RemoteSafeHandle git_remote_create_anonymous(RepositorySafeHandle repo, string url, string refspec) + public static unsafe RemoteHandle git_remote_create_anonymous(RepositoryHandle repo, string url) { - using (ThreadAffinity()) - { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_create_anonymous(out handle, repo, url, refspec); - Ensure.ZeroResult(res); + git_remote* handle; + int res = NativeMethods.git_remote_create_anonymous(out handle, repo, url); + Ensure.ZeroResult(res); - return handle; - } + return new RemoteHandle(handle, true); } - public static void git_remote_connect(RemoteSafeHandle remote, GitDirection direction) + public static unsafe void git_remote_connect(RemoteHandle remote, GitDirection direction, ref GitRemoteCallbacks remoteCallbacks, ref GitProxyOptions proxyOptions) { - using (ThreadAffinity()) + GitStrArrayManaged customHeaders = new GitStrArrayManaged(); + + try { - int res = NativeMethods.git_remote_connect(remote, direction); + int res = NativeMethods.git_remote_connect(remote, direction, ref remoteCallbacks, ref proxyOptions, ref customHeaders.Array); Ensure.ZeroResult(res); } - } - - public static void git_remote_delete(RepositorySafeHandle repo, string name) - { - using (ThreadAffinity()) + catch (Exception) { - int res = NativeMethods.git_remote_delete(repo, name); - - if (res == (int)GitErrorCode.NotFound) - { - return; - } - - Ensure.ZeroResult(res); + customHeaders.Dispose(); + throw; } } - public static void git_remote_disconnect(RemoteSafeHandle remote) + public static unsafe void git_remote_delete(RepositoryHandle repo, string name) { - using (ThreadAffinity()) + int res = NativeMethods.git_remote_delete(repo, name); + + if (res == (int)GitErrorCode.NotFound) { - NativeMethods.git_remote_disconnect(remote); + return; } + + Ensure.ZeroResult(res); } - public static GitRefSpecHandle git_remote_get_refspec(RemoteSafeHandle remote, int n) + public static unsafe git_refspec* git_remote_get_refspec(RemoteHandle remote, int n) { return NativeMethods.git_remote_get_refspec(remote, (UIntPtr)n); } - public static int git_remote_refspec_count(RemoteSafeHandle remote) + public static unsafe int git_remote_refspec_count(RemoteHandle remote) { return (int)NativeMethods.git_remote_refspec_count(remote); } - public static IList git_remote_get_fetch_refspecs(RemoteSafeHandle remote) + public static unsafe IList git_remote_get_fetch_refspecs(RemoteHandle remote) { - using (ThreadAffinity()) - { - var array = new GitStrArrayNative(); + var array = new GitStrArrayNative(); - try - { - int res = NativeMethods.git_remote_get_fetch_refspecs(out array.Array, remote); - Ensure.ZeroResult(res); + try + { + int res = NativeMethods.git_remote_get_fetch_refspecs(out array.Array, remote); + Ensure.ZeroResult(res); - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static IList git_remote_get_push_refspecs(RemoteSafeHandle remote) + public static unsafe IList git_remote_get_push_refspecs(RemoteHandle remote) { - using (ThreadAffinity()) - { - var array = new GitStrArrayNative(); + var array = new GitStrArrayNative(); - try - { - int res = NativeMethods.git_remote_get_push_refspecs(out array.Array, remote); - Ensure.ZeroResult(res); + try + { + int res = NativeMethods.git_remote_get_push_refspecs(out array.Array, remote); + Ensure.ZeroResult(res); - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static void git_remote_set_fetch_refspecs(RemoteSafeHandle remote, IEnumerable refSpecs) + public static unsafe void git_remote_push(RemoteHandle remote, IEnumerable refSpecs, GitPushOptions opts) { - using (ThreadAffinity()) + var array = new GitStrArrayManaged(); + + try { - var array = new GitStrArrayManaged(); + array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); - try - { - array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); - - int res = NativeMethods.git_remote_set_fetch_refspecs(remote, ref array.Array); - Ensure.ZeroResult(res); - } - finally - { - array.Dispose(); - } + int res = NativeMethods.git_remote_push(remote, ref array.Array, opts); + Ensure.ZeroResult(res); + } + finally + { + array.Dispose(); } } - public static void git_remote_set_push_refspecs(RemoteSafeHandle remote, IEnumerable refSpecs) + public static unsafe void git_remote_set_url(RepositoryHandle repo, string remote, string url) { - using (ThreadAffinity()) - { - var array = new GitStrArrayManaged(); + int res = NativeMethods.git_remote_set_url(repo, remote, url); + Ensure.ZeroResult(res); + } - try - { - array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); + public static unsafe void git_remote_add_fetch(RepositoryHandle repo, string remote, string url) + { + int res = NativeMethods.git_remote_add_fetch(repo, remote, url); + Ensure.ZeroResult(res); + } - int res = NativeMethods.git_remote_set_push_refspecs(remote, ref array.Array); - Ensure.ZeroResult(res); - } - finally - { - array.Dispose(); - } - } + public static unsafe void git_remote_set_pushurl(RepositoryHandle repo, string remote, string url) + { + int res = NativeMethods.git_remote_set_pushurl(repo, remote, url); + Ensure.ZeroResult(res); } - public static void git_remote_set_url(RemoteSafeHandle remote, string url) + public static unsafe void git_remote_add_push(RepositoryHandle repo, string remote, string url) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_set_url(remote, url); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_remote_add_push(repo, remote, url); + Ensure.ZeroResult(res); } - public static void git_remote_fetch(RemoteSafeHandle remote, Signature signature, string logMessage) + public static unsafe void git_remote_fetch( + RemoteHandle remote, IEnumerable refSpecs, + GitFetchOptions fetchOptions, string logMessage) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) + var array = new GitStrArrayManaged(); + + try { - var array = new GitStrArrayNative(); + array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); - try - { - int res = NativeMethods.git_remote_fetch(remote, ref array.Array, sigHandle, logMessage); - Ensure.ZeroResult(res); - } - finally - { - array.Dispose(); - } + int res = NativeMethods.git_remote_fetch(remote, ref array.Array, fetchOptions, logMessage); + Ensure.ZeroResult(res); + } + finally + { + array.Dispose(); } - } - - public static void git_remote_free(IntPtr remote) - { - NativeMethods.git_remote_free(remote); } public static bool git_remote_is_valid_name(string refname) @@ -2038,160 +2303,142 @@ public static bool git_remote_is_valid_name(string refname) return (res == 1); } - public static IList git_remote_list(RepositorySafeHandle repo) + public static unsafe IList git_remote_list(RepositoryHandle repo) { - using (ThreadAffinity()) - { - var array = new GitStrArrayNative(); + var array = new GitStrArrayNative(); - try - { - int res = NativeMethods.git_remote_list(out array.Array, repo); - Ensure.ZeroResult(res); + try + { + int res = NativeMethods.git_remote_list(out array.Array, repo); + Ensure.ZeroResult(res); - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static IEnumerable git_remote_ls(Repository repository, RemoteSafeHandle remote) + public static unsafe IEnumerable git_remote_ls(Repository repository, RemoteHandle remote) { - IntPtr heads; + git_remote_head** heads; UIntPtr count; - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_ls(out heads, out count, remote); - Ensure.ZeroResult(res); - } - - var intCount = (int)count.ToUInt32(); - - if (intCount < 0) - { - throw new OverflowException(); - } + int res = NativeMethods.git_remote_ls(out heads, out count, remote); + Ensure.ZeroResult(res); - var refs = new List(); - IntPtr currentHead = heads; + var intCount = checked(count.ToUInt32()); + var directRefs = new Dictionary(); + var symRefs = new Dictionary(); for (int i = 0; i < intCount; i++) { - var remoteHead = Marshal.ReadIntPtr(currentHead).MarshalAs(); + git_remote_head* currentHead = heads[i]; + string name = LaxUtf8Marshaler.FromNative(currentHead->Name); + string symRefTarget = LaxUtf8Marshaler.FromNative(currentHead->SymrefTarget); // The name pointer should never be null - if it is, // this indicates a bug somewhere (libgit2, server, etc). - if (remoteHead.NamePtr == IntPtr.Zero) + if (string.IsNullOrEmpty(name)) { throw new InvalidOperationException("Not expecting null value for reference name."); } - string name = LaxUtf8Marshaler.FromNative(remoteHead.NamePtr); - refs.Add(new DirectReference(name, repository, remoteHead.Oid)); + if (!string.IsNullOrEmpty(symRefTarget)) + { + symRefs.Add(name, symRefTarget); + } + else + { + directRefs.Add(name, new DirectReference(name, repository, new ObjectId(currentHead->Oid.Id))); + } + } + + for (int i = 0; i < symRefs.Count; i++) + { + var symRef = symRefs.ElementAt(i); + + if (!directRefs.ContainsKey(symRef.Value)) + { + throw new InvalidOperationException("Symbolic reference target not found in direct reference results."); + } - currentHead = IntPtr.Add(currentHead, IntPtr.Size); + directRefs.Add(symRef.Key, new SymbolicReference(repository, symRef.Key, symRef.Value, directRefs[symRef.Value])); } + var refs = directRefs.Values.ToList(); + refs.Sort((r1, r2) => string.CompareOrdinal(r1.CanonicalName, r2.CanonicalName)); + return refs; } - public static RemoteSafeHandle git_remote_load(RepositorySafeHandle repo, string name, bool throwsIfNotFound) + public static unsafe RemoteHandle git_remote_lookup(RepositoryHandle repo, string name, bool throwsIfNotFound) { - using (ThreadAffinity()) - { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_load(out handle, repo, name); - - if (res == (int)GitErrorCode.NotFound && !throwsIfNotFound) - { - return null; - } + git_remote* handle; + int res = NativeMethods.git_remote_lookup(out handle, repo, name); - Ensure.ZeroResult(res); - return handle; + if (res == (int)GitErrorCode.NotFound && !throwsIfNotFound) + { + return null; } + + Ensure.ZeroResult(res); + return new RemoteHandle(handle, true); } - public static string git_remote_name(RemoteSafeHandle remote) + public static unsafe string git_remote_name(RemoteHandle remote) { return NativeMethods.git_remote_name(remote); } - public static void git_remote_rename(RepositorySafeHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) + public static unsafe void git_remote_rename(RepositoryHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) { - using (ThreadAffinity()) + if (callback == null) { - using (RemoteSafeHandle remote = git_remote_load(repo, name, false)) - { - if (remote == null) - { - return; - } + callback = problem => { }; + } - if (callback == null) - { - callback = problem => {}; - } + var array = new GitStrArrayNative(); - var array = new GitStrArrayNative(); + try + { + int res = NativeMethods.git_remote_rename(ref array.Array, + repo, + name, + new_name); - try - { - int res = NativeMethods.git_remote_rename( - ref array.Array, - remote, - new_name); + if (res == (int)GitErrorCode.NotFound) + { + throw new NotFoundException("Remote '{0}' does not exist and cannot be renamed.", name); + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - foreach (var item in array.ReadStrings()) - { - callback(item); - } - } - finally - { - array.Dispose(); - } + foreach (var item in array.ReadStrings()) + { + callback(item); } } - } - - public static void git_remote_save(RemoteSafeHandle remote) - { - using (ThreadAffinity()) + finally { - int res = NativeMethods.git_remote_save(remote); - Ensure.ZeroResult(res); + array.Dispose(); } } - public static void git_remote_set_autotag(RemoteSafeHandle remote, TagFetchMode value) - { - NativeMethods.git_remote_set_autotag(remote, value); - } - - public static void git_remote_set_callbacks(RemoteSafeHandle remote, ref GitRemoteCallbacks callbacks) + public static unsafe void git_remote_set_autotag(RepositoryHandle repo, string remote, TagFetchMode value) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_set_callbacks(remote, ref callbacks); - Ensure.ZeroResult(res); - } + NativeMethods.git_remote_set_autotag(repo, remote, value); } - public static string git_remote_url(RemoteSafeHandle remote) + public static unsafe string git_remote_url(RemoteHandle remote) { return NativeMethods.git_remote_url(remote); } - public static bool git_remote_supported_url(string url) + public static unsafe string git_remote_pushurl(RemoteHandle remote) { - return NativeMethods.git_remote_supported_url(url); + return NativeMethods.git_remote_pushurl(remote); } #endregion @@ -2203,95 +2450,80 @@ public static FilePath git_repository_discover(FilePath start_path) return ConvertPath(buf => NativeMethods.git_repository_discover(buf, start_path, false, null)); } - public static bool git_repository_head_detached(RepositorySafeHandle repo) + public static unsafe bool git_repository_head_detached(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_head_detached); } - public static ICollection git_repository_fetchhead_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_repository_fetchhead_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_fetchhead_foreach( - repo, - (IntPtr w, IntPtr x, ref GitOid y, bool z, IntPtr p) - => c(LaxUtf8Marshaler.FromNative(w), LaxUtf8Marshaler.FromNative(x), y, z, p), IntPtr.Zero), - GitErrorCode.NotFound); - } - - public static void git_repository_free(IntPtr repo) - { - NativeMethods.git_repository_free(repo); + return git_foreach(resultSelector, + c => NativeMethods.git_repository_fetchhead_foreach(repo, + (IntPtr w, IntPtr x, ref GitOid y, bool z, IntPtr p) + => c(LaxUtf8Marshaler.FromNative(w), LaxUtf8Marshaler.FromNative(x), y, z, p), + IntPtr.Zero), + GitErrorCode.NotFound); } - public static bool git_repository_head_unborn(RepositorySafeHandle repo) + public static bool git_repository_head_unborn(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_head_unborn); } - public static IndexSafeHandle git_repository_index(RepositorySafeHandle repo) + public static unsafe IndexHandle git_repository_index(RepositoryHandle repo) { - using (ThreadAffinity()) - { - IndexSafeHandle handle; - int res = NativeMethods.git_repository_index(out handle, repo); - Ensure.ZeroResult(res); + git_index* handle; + int res = NativeMethods.git_repository_index(out handle, repo); + Ensure.ZeroResult(res); - return handle; - } + return new IndexHandle(handle, true); } - public static RepositorySafeHandle git_repository_init_ext( + public static unsafe RepositoryHandle git_repository_init_ext( FilePath workdirPath, FilePath gitdirPath, bool isBare) { - using (ThreadAffinity()) using (var opts = GitRepositoryInitOptions.BuildFrom(workdirPath, isBare)) { - RepositorySafeHandle repo; + git_repository* repo; int res = NativeMethods.git_repository_init_ext(out repo, gitdirPath, opts); Ensure.ZeroResult(res); - return repo; + return new RepositoryHandle(repo, true); } } - public static bool git_repository_is_bare(RepositorySafeHandle repo) + public static unsafe bool git_repository_is_bare(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_is_bare); } - public static bool git_repository_is_shallow(RepositorySafeHandle repo) + public static unsafe bool git_repository_is_shallow(RepositoryHandle repo) { return RepositoryStateChecker(repo, NativeMethods.git_repository_is_shallow); } - public static void git_repository_state_cleanup(RepositorySafeHandle repo) + public static unsafe void git_repository_state_cleanup(RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_repository_state_cleanup(repo); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_repository_state_cleanup(repo); + Ensure.ZeroResult(res); } - public static ICollection git_repository_mergehead_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_repository_mergehead_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_mergehead_foreach( - repo, (ref GitOid x, IntPtr p) => c(x, p), IntPtr.Zero), - GitErrorCode.NotFound); + return git_foreach(resultSelector, + c => NativeMethods.git_repository_mergehead_foreach(repo, + (ref GitOid x, IntPtr p) => c(x, p), IntPtr.Zero), + GitErrorCode.NotFound); } - public static string git_repository_message(RepositorySafeHandle repo) + public static unsafe string git_repository_message(RepositoryHandle repo) { - using (ThreadAffinity()) using (var buf = new GitBuf()) { int res = NativeMethods.git_repository_message(buf, repo); @@ -2305,135 +2537,134 @@ public static string git_repository_message(RepositorySafeHandle repo) } } - public static ObjectDatabaseSafeHandle git_repository_odb(RepositorySafeHandle repo) + public static unsafe ObjectDatabaseHandle git_repository_odb(RepositoryHandle repo) { - using (ThreadAffinity()) - { - ObjectDatabaseSafeHandle handle; - int res = NativeMethods.git_repository_odb(out handle, repo); - Ensure.ZeroResult(res); + git_odb* handle; + int res = NativeMethods.git_repository_odb(out handle, repo); + Ensure.ZeroResult(res); - return handle; - } + return new ObjectDatabaseHandle(handle, true); } - public static RepositorySafeHandle git_repository_open(string path) + public static unsafe RepositoryHandle git_repository_open(string path) { - using (ThreadAffinity()) - { - RepositorySafeHandle repo; - int res = NativeMethods.git_repository_open(out repo, path); + git_repository* repo; + int res = NativeMethods.git_repository_open(out repo, path); - if (res == (int)GitErrorCode.NotFound) - { - throw new RepositoryNotFoundException(String.Format(CultureInfo.InvariantCulture, "Path '{0}' doesn't point at a valid Git repository or workdir.", path)); - } + if (res == (int)GitErrorCode.NotFound) + { + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return repo; - } + return new RepositoryHandle(repo, true); } - public static void git_repository_open_ext(string path, RepositoryOpenFlags flags, string ceilingDirs) + public static unsafe RepositoryHandle git_repository_new() { - using (ThreadAffinity()) - { - int res; + git_repository* repo; + int res = NativeMethods.git_repository_new(out repo); - using (var repo = new NullRepositorySafeHandle()) - { - res = NativeMethods.git_repository_open_ext(repo, path, flags, ceilingDirs); - } + Ensure.ZeroResult(res); - if (res == (int)GitErrorCode.NotFound) - { - throw new RepositoryNotFoundException(String.Format(CultureInfo.InvariantCulture, "Path '{0}' doesn't point at a valid Git repository or workdir.", path)); - } + return new RepositoryHandle(repo, true); + } - Ensure.ZeroResult(res); + public static unsafe void git_repository_open_ext(string path, RepositoryOpenFlags flags, string ceilingDirs) + { + int res; + git_repository* repo; + + res = NativeMethods.git_repository_open_ext(out repo, path, flags, ceilingDirs); + NativeMethods.git_repository_free(repo); + + if (res == (int)GitErrorCode.NotFound) + { + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); } + + Ensure.ZeroResult(res); } - public static FilePath git_repository_path(RepositorySafeHandle repo) + public static unsafe FilePath git_repository_path(RepositoryHandle repo) { return NativeMethods.git_repository_path(repo); } - public static void git_repository_set_config(RepositorySafeHandle repo, ConfigurationSafeHandle config) + public static unsafe int git_repository_set_config(RepositoryHandle repo, ConfigurationHandle config) + { + return NativeMethods.git_repository_set_config(repo, config); + } + + public static unsafe void git_repository_set_ident(RepositoryHandle repo, string name, string email) { - NativeMethods.git_repository_set_config(repo, config); + int res = NativeMethods.git_repository_set_ident(repo, name, email); + Ensure.ZeroResult(res); } - public static void git_repository_set_index(RepositorySafeHandle repo, IndexSafeHandle index) + public static unsafe int git_repository_set_index(RepositoryHandle repo, IndexHandle index) { - NativeMethods.git_repository_set_index(repo, index); + return NativeMethods.git_repository_set_index(repo, index); } - public static void git_repository_set_workdir(RepositorySafeHandle repo, FilePath workdir) + public static unsafe void git_repository_set_workdir(RepositoryHandle repo, FilePath workdir) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_repository_set_workdir(repo, workdir, false); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_repository_set_workdir(repo, workdir, false); + Ensure.ZeroResult(res); } - public static CurrentOperation git_repository_state(RepositorySafeHandle repo) + public static unsafe CurrentOperation git_repository_state(RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_repository_state(repo); - Ensure.Int32Result(res); - return (CurrentOperation)res; - } + int res = NativeMethods.git_repository_state(repo); + Ensure.Int32Result(res); + return (CurrentOperation)res; } - public static FilePath git_repository_workdir(RepositorySafeHandle repo) + public static unsafe FilePath git_repository_workdir(RepositoryHandle repo) { return NativeMethods.git_repository_workdir(repo); } - public static void git_repository_set_head_detached(RepositorySafeHandle repo, ObjectId commitish, - Signature signature, string logMessage) + public static FilePath git_repository_workdir(IntPtr repo) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - GitOid oid = commitish.Oid; - int res = NativeMethods.git_repository_set_head_detached(repo, ref oid, sigHandle, logMessage); - Ensure.ZeroResult(res); - } + return NativeMethods.git_repository_workdir(repo); } - public static void git_repository_set_head(RepositorySafeHandle repo, string refname, - Signature signature, string logMessage) + public static unsafe void git_repository_set_head_detached(RepositoryHandle repo, ObjectId commitish) { - using (ThreadAffinity()) - using (var sigHandle = signature.BuildHandle()) - { - int res = NativeMethods.git_repository_set_head(repo, refname, sigHandle, logMessage); - Ensure.ZeroResult(res); - } + GitOid oid = commitish.Oid; + int res = NativeMethods.git_repository_set_head_detached(repo, ref oid); + Ensure.ZeroResult(res); + } + + public static unsafe void git_repository_set_head_detached_from_annotated(RepositoryHandle repo, AnnotatedCommitHandle commit) + { + int res = NativeMethods.git_repository_set_head_detached_from_annotated(repo, commit); + Ensure.ZeroResult(res); + } + + public static unsafe void git_repository_set_head(RepositoryHandle repo, string refname) + { + int res = NativeMethods.git_repository_set_head(repo, refname); + Ensure.ZeroResult(res); } #endregion #region git_reset_ - public static void git_reset( - RepositorySafeHandle repo, + public static unsafe void git_reset( + RepositoryHandle repo, ObjectId committishId, ResetMode resetKind, - ref GitCheckoutOpts checkoutOptions, - Signature signature, - string logMessage) + ref GitCheckoutOpts checkoutOptions) { - using (ThreadAffinity()) using (var osw = new ObjectSafeWrapper(committishId, repo)) - using (var sigHandle = signature.BuildHandle()) { - int res = NativeMethods.git_reset(repo, osw.ObjectPtr, resetKind, ref checkoutOptions, sigHandle, logMessage); + int res = NativeMethods.git_reset(repo, osw.ObjectPtr, resetKind, ref checkoutOptions); Ensure.ZeroResult(res); } } @@ -2442,12 +2673,11 @@ public static void git_reset( #region git_revert_ - public static void git_revert( - RepositorySafeHandle repo, + public static unsafe void git_revert( + RepositoryHandle repo, ObjectId commit, GitRevertOpts opts) { - using (ThreadAffinity()) using (var nativeCommit = git_object_lookup(repo, commit, GitObjectType.Commit)) { int res = NativeMethods.git_revert(repo, nativeCommit, opts); @@ -2455,36 +2685,49 @@ public static void git_revert( } } + internal static unsafe IndexHandle git_revert_commit(RepositoryHandle repo, ObjectHandle revertCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_revert_commit(out index, repo, revertCommit, ourCommit, mainline, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + return new IndexHandle(index, true); + } #endregion #region git_revparse_ - public static Tuple git_revparse_ext(RepositorySafeHandle repo, string objectish) + public static unsafe Tuple git_revparse_ext(RepositoryHandle repo, string objectish) { - using (ThreadAffinity()) - { - GitObjectSafeHandle obj; - ReferenceSafeHandle reference; - int res = NativeMethods.git_revparse_ext(out obj, out reference, repo, objectish); + git_object* obj; + git_reference* reference; + int res = NativeMethods.git_revparse_ext(out obj, out reference, repo, objectish); - switch (res) - { - case (int)GitErrorCode.NotFound: - return null; - - case (int)GitErrorCode.Ambiguous: - throw new AmbiguousSpecificationException(string.Format(CultureInfo.InvariantCulture, "Provided abbreviated ObjectId '{0}' is too short.", objectish)); + switch (res) + { + case (int)GitErrorCode.NotFound: + return null; - default: - Ensure.ZeroResult(res); - break; - } + case (int)GitErrorCode.Ambiguous: + throw new AmbiguousSpecificationException("Provided abbreviated ObjectId '{0}' is too short.", + objectish); - return new Tuple(obj, reference); + default: + Ensure.ZeroResult(res); + break; } + + return new Tuple(new ObjectHandle(obj, true), new ReferenceHandle(reference, true)); } - public static GitObjectSafeHandle git_revparse_single(RepositorySafeHandle repo, string objectish) + public static ObjectHandle git_revparse_single(RepositoryHandle repo, string objectish) { var handles = git_revparse_ext(repo, objectish); @@ -2502,121 +2745,102 @@ public static GitObjectSafeHandle git_revparse_single(RepositorySafeHandle repo, #region git_revwalk_ - public static void git_revwalk_free(IntPtr walker) + public static unsafe void git_revwalk_hide(RevWalkerHandle walker, ObjectId commit_id) { - NativeMethods.git_revwalk_free(walker); - } - - public static void git_revwalk_hide(RevWalkerSafeHandle walker, ObjectId commit_id) - { - using (ThreadAffinity()) - { - GitOid oid = commit_id.Oid; - int res = NativeMethods.git_revwalk_hide(walker, ref oid); - Ensure.ZeroResult(res); - } + GitOid oid = commit_id.Oid; + int res = NativeMethods.git_revwalk_hide(walker, ref oid); + Ensure.ZeroResult(res); } - public static RevWalkerSafeHandle git_revwalk_new(RepositorySafeHandle repo) + public static unsafe RevWalkerHandle git_revwalk_new(RepositoryHandle repo) { - using (ThreadAffinity()) - { - RevWalkerSafeHandle handle; - int res = NativeMethods.git_revwalk_new(out handle, repo); - Ensure.ZeroResult(res); + git_revwalk* handle; + int res = NativeMethods.git_revwalk_new(out handle, repo); + Ensure.ZeroResult(res); - return handle; - } + return new RevWalkerHandle(handle, true); } - public static ObjectId git_revwalk_next(RevWalkerSafeHandle walker) + public static unsafe ObjectId git_revwalk_next(RevWalkerHandle walker) { - using (ThreadAffinity()) - { - GitOid ret; - int res = NativeMethods.git_revwalk_next(out ret, walker); + GitOid ret; + int res = NativeMethods.git_revwalk_next(out ret, walker); - if (res == (int)GitErrorCode.IterOver) - { - return null; - } + if (res == (int)GitErrorCode.IterOver) + { + return null; + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return ret; - } + return ret; } - public static void git_revwalk_push(RevWalkerSafeHandle walker, ObjectId id) + public static unsafe void git_revwalk_push(RevWalkerHandle walker, ObjectId id) { - using (ThreadAffinity()) - { - GitOid oid = id.Oid; - int res = NativeMethods.git_revwalk_push(walker, ref oid); - Ensure.ZeroResult(res); - } + GitOid oid = id.Oid; + int res = NativeMethods.git_revwalk_push(walker, ref oid); + Ensure.ZeroResult(res); } - public static void git_revwalk_reset(RevWalkerSafeHandle walker) + public static unsafe void git_revwalk_reset(RevWalkerHandle walker) { NativeMethods.git_revwalk_reset(walker); } - public static void git_revwalk_sorting(RevWalkerSafeHandle walker, CommitSortStrategies options) + public static unsafe int git_revwalk_sorting(RevWalkerHandle walker, CommitSortStrategies options) { - NativeMethods.git_revwalk_sorting(walker, options); + return NativeMethods.git_revwalk_sorting(walker, options); } - public static void git_revwalk_simplify_first_parent(RevWalkerSafeHandle walker) + public static unsafe int git_revwalk_simplify_first_parent(RevWalkerHandle walker) { - NativeMethods.git_revwalk_simplify_first_parent(walker); + return NativeMethods.git_revwalk_simplify_first_parent(walker); } #endregion #region git_signature_ - public static void git_signature_free(IntPtr signature) + public static unsafe SignatureHandle git_signature_new(string name, string email, DateTimeOffset when) { - NativeMethods.git_signature_free(signature); + git_signature* ptr; + + int res = NativeMethods.git_signature_new(out ptr, name, email, when.ToUnixTimeSeconds(), + (int)when.Offset.TotalMinutes); + Ensure.ZeroResult(res); + + return new SignatureHandle(ptr, true); } - public static SignatureSafeHandle git_signature_new(string name, string email, DateTimeOffset when) + public static unsafe SignatureHandle git_signature_now(string name, string email) { - using (ThreadAffinity()) - { - SignatureSafeHandle handle; - int res = NativeMethods.git_signature_new(out handle, name, email, when.ToSecondsSinceEpoch(), - (int)when.Offset.TotalMinutes); - Ensure.ZeroResult(res); + git_signature* ptr; + int res = NativeMethods.git_signature_now(out ptr, name, email); + Ensure.ZeroResult(res); - return handle; - } + return new SignatureHandle(ptr, true); } - public static IntPtr git_signature_dup(IntPtr sig) + public static unsafe git_signature* git_signature_dup(git_signature* sig) { - using (ThreadAffinity()) - { - IntPtr handle; - int res = NativeMethods.git_signature_dup(out handle, sig); - Ensure.ZeroResult(res); - return handle; - } + git_signature* handle; + int res = NativeMethods.git_signature_dup(out handle, sig); + Ensure.ZeroResult(res); + return handle; } #endregion #region git_stash_ - public static ObjectId git_stash_save( - RepositorySafeHandle repo, + public static unsafe ObjectId git_stash_save( + RepositoryHandle repo, Signature stasher, string prettifiedMessage, StashModifiers options) { - using (ThreadAffinity()) - using (SignatureSafeHandle sigHandle = stasher.BuildHandle()) + using (SignatureHandle sigHandle = stasher.BuildHandle()) { GitOid stashOid; @@ -2633,83 +2857,107 @@ public static ObjectId git_stash_save( } } - public static ICollection git_stash_foreach( - RepositorySafeHandle repo, + public static unsafe ICollection git_stash_foreach( + RepositoryHandle repo, Func resultSelector) { - return git_foreach( - resultSelector, - c => NativeMethods.git_stash_foreach( - repo, (UIntPtr i, IntPtr m, ref GitOid x, IntPtr p) => c((int)i, m, x, p), IntPtr.Zero), - GitErrorCode.NotFound); + return git_foreach(resultSelector, + c => NativeMethods.git_stash_foreach(repo, + (UIntPtr i, IntPtr m, ref GitOid x, IntPtr p) + => c((int)i, m, x, p), + IntPtr.Zero), + GitErrorCode.NotFound); } - public static void git_stash_drop(RepositorySafeHandle repo, int index) + public static unsafe void git_stash_drop(RepositoryHandle repo, int index) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_stash_drop(repo, (UIntPtr) index); - Ensure.BooleanResult(res); - } + int res = NativeMethods.git_stash_drop(repo, (UIntPtr)index); + Ensure.BooleanResult(res); } - #endregion - - #region git_status_ - - public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath path) + private static StashApplyStatus get_stash_status(int res) { - using (ThreadAffinity()) + if (res == (int)GitErrorCode.Conflict) { - FileStatus status; - int res = NativeMethods.git_status_file(out status, repo, path); + return StashApplyStatus.Conflicts; + } - switch (res) - { - case (int)GitErrorCode.NotFound: - return FileStatus.Nonexistent; + if (res == (int)GitErrorCode.Uncommitted) + { + return StashApplyStatus.UncommittedChanges; + } - case (int)GitErrorCode.Ambiguous: - throw new AmbiguousSpecificationException(string.Format(CultureInfo.InvariantCulture, "More than one file matches the pathspec '{0}'. You can either force a literal path evaluation (GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH), or use git_status_foreach().", path)); + if (res == (int)GitErrorCode.NotFound) + { + return StashApplyStatus.NotFound; + } - default: - Ensure.ZeroResult(res); - break; - } + Ensure.ZeroResult(res); + return StashApplyStatus.Applied; + } - return status; - } + public static unsafe StashApplyStatus git_stash_apply( + RepositoryHandle repo, + int index, + GitStashApplyOpts opts) + { + return get_stash_status(NativeMethods.git_stash_apply(repo, (UIntPtr)index, opts)); } - public static StatusListSafeHandle git_status_list_new(RepositorySafeHandle repo, GitStatusOptions options) + public static unsafe StashApplyStatus git_stash_pop( + RepositoryHandle repo, + int index, + GitStashApplyOpts opts) { - using (ThreadAffinity()) - { - StatusListSafeHandle handle; - int res = NativeMethods.git_status_list_new(out handle, repo, options); - Ensure.ZeroResult(res); - return handle; - } + return get_stash_status(NativeMethods.git_stash_pop(repo, (UIntPtr)index, opts)); } - public static int git_status_list_entrycount(StatusListSafeHandle list) + #endregion + + #region git_status_ + + public static unsafe FileStatus git_status_file(RepositoryHandle repo, FilePath path) { - using (ThreadAffinity()) + FileStatus status; + int res = NativeMethods.git_status_file(out status, repo, path); + + switch (res) { - int res = NativeMethods.git_status_list_entrycount(list); - Ensure.Int32Result(res); - return res; + case (int)GitErrorCode.NotFound: + return FileStatus.Nonexistent; + + case (int)GitErrorCode.Ambiguous: + throw new AmbiguousSpecificationException("More than one file matches the pathspec '{0}'. " + + "You can either force a literal path evaluation " + + "(GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH), or use git_status_foreach().", + path); + + default: + Ensure.ZeroResult(res); + break; } + + return status; } - public static StatusEntrySafeHandle git_status_byindex(StatusListSafeHandle list, long idx) + public static unsafe StatusListHandle git_status_list_new(RepositoryHandle repo, GitStatusOptions options) { - return NativeMethods.git_status_byindex(list, (UIntPtr)idx); + git_status_list* ptr; + int res = NativeMethods.git_status_list_new(out ptr, repo, options); + Ensure.ZeroResult(res); + return new StatusListHandle(ptr, true); + } + + public static unsafe int git_status_list_entrycount(StatusListHandle list) + { + int res = NativeMethods.git_status_list_entrycount(list); + Ensure.Int32Result(res); + return res; } - public static void git_status_list_free(IntPtr statusList) + public static unsafe git_status_entry* git_status_byindex(StatusListHandle list, long idx) { - NativeMethods.git_status_list_free(statusList); + return NativeMethods.git_status_byindex(list, (UIntPtr)idx); } #endregion @@ -2720,129 +2968,125 @@ public static void git_status_list_free(IntPtr statusList) /// Returns a handle to the corresponding submodule, /// or an invalid handle if a submodule is not found. /// - public static SubmoduleSafeHandle git_submodule_lookup(RepositorySafeHandle repo, FilePath name) + public static unsafe SubmoduleHandle git_submodule_lookup(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - SubmoduleSafeHandle reference; - var res = NativeMethods.git_submodule_lookup(out reference, repo, name); + git_submodule* submodule; + var res = NativeMethods.git_submodule_lookup(out submodule, repo, name); - switch (res) - { - case (int)GitErrorCode.NotFound: - case (int)GitErrorCode.Exists: - case (int)GitErrorCode.OrphanedHead: - return null; + switch (res) + { + case (int)GitErrorCode.NotFound: + case (int)GitErrorCode.Exists: + case (int)GitErrorCode.OrphanedHead: + return null; - default: - Ensure.ZeroResult(res); - return reference; - } + default: + Ensure.ZeroResult(res); + return new SubmoduleHandle(submodule, true); } } - public static ICollection git_submodule_foreach(RepositorySafeHandle repo, Func resultSelector) + public static unsafe string git_submodule_resolve_url(RepositoryHandle repo, string url) { - return git_foreach(resultSelector, c => NativeMethods.git_submodule_foreach(repo, (x, y, p) => c(x, y, p), IntPtr.Zero)); - } - - public static void git_submodule_add_to_index(SubmoduleSafeHandle submodule, bool write_index) - { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - var res = NativeMethods.git_submodule_add_to_index(submodule, write_index); + int res = NativeMethods.git_submodule_resolve_url(buf, repo, url); + Ensure.ZeroResult(res); + return LaxUtf8Marshaler.FromNative(buf.ptr); } } - public static void git_submodule_save(SubmoduleSafeHandle submodule) + public static unsafe ICollection git_submodule_foreach(RepositoryHandle repo, Func resultSelector) { - using (ThreadAffinity()) - { - var res = NativeMethods.git_submodule_save(submodule); - Ensure.ZeroResult(res); - } + return git_foreach(resultSelector, c => NativeMethods.git_submodule_foreach(repo, (x, y, p) => c(x, y, p), IntPtr.Zero)); + } + + public static unsafe void git_submodule_add_to_index(SubmoduleHandle submodule, bool write_index) + { + var res = NativeMethods.git_submodule_add_to_index(submodule, write_index); + Ensure.ZeroResult(res); } - public static void git_submodule_free(IntPtr submodule) + public static unsafe void git_submodule_update(SubmoduleHandle submodule, bool init, ref GitSubmoduleUpdateOptions options) { - NativeMethods.git_submodule_free(submodule); + var res = NativeMethods.git_submodule_update(submodule, init, ref options); + Ensure.ZeroResult(res); } - public static string git_submodule_path(SubmoduleSafeHandle submodule) + public static unsafe string git_submodule_path(SubmoduleHandle submodule) { return NativeMethods.git_submodule_path(submodule); } - public static string git_submodule_url(SubmoduleSafeHandle submodule) + public static unsafe string git_submodule_url(SubmoduleHandle submodule) { return NativeMethods.git_submodule_url(submodule); } - public static ObjectId git_submodule_index_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_index_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_index_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_index_id(submodule)); } - public static ObjectId git_submodule_head_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_head_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_head_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_head_id(submodule)); } - public static ObjectId git_submodule_wd_id(SubmoduleSafeHandle submodule) + public static unsafe ObjectId git_submodule_wd_id(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_wd_id(submodule).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_wd_id(submodule)); } - public static SubmoduleIgnore git_submodule_ignore(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleIgnore git_submodule_ignore(SubmoduleHandle submodule) { return NativeMethods.git_submodule_ignore(submodule); } - public static SubmoduleUpdate git_submodule_update(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleUpdate git_submodule_update_strategy(SubmoduleHandle submodule) { - return NativeMethods.git_submodule_update(submodule); + return NativeMethods.git_submodule_update_strategy(submodule); } - public static bool git_submodule_fetch_recurse_submodules(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules(SubmoduleHandle submodule) { return NativeMethods.git_submodule_fetch_recurse_submodules(submodule); } - public static void git_submodule_reload(SubmoduleSafeHandle submodule) + public static unsafe void git_submodule_reload(SubmoduleHandle submodule) { - using (ThreadAffinity()) - { - var res = NativeMethods.git_submodule_reload(submodule, false); - Ensure.ZeroResult(res); - } + var res = NativeMethods.git_submodule_reload(submodule, false); + Ensure.ZeroResult(res); } - public static SubmoduleStatus git_submodule_status(SubmoduleSafeHandle submodule) + public static unsafe SubmoduleStatus git_submodule_status(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - SubmoduleStatus status; - var res = NativeMethods.git_submodule_status(out status, submodule); - Ensure.ZeroResult(res); - return status; - } + SubmoduleStatus status; + var res = NativeMethods.git_submodule_status(out status, repo, name, GitSubmoduleIgnore.Unspecified); + Ensure.ZeroResult(res); + return status; + } + + public static unsafe void git_submodule_init(SubmoduleHandle submodule, bool overwrite) + { + var res = NativeMethods.git_submodule_init(submodule, overwrite); + Ensure.ZeroResult(res); } #endregion #region git_tag_ - public static ObjectId git_tag_annotation_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_tag_annotation_create( + RepositoryHandle repo, string name, GitObject target, Signature tagger, string message) { - using (ThreadAffinity()) using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) - using (SignatureSafeHandle sigHandle = tagger.BuildHandle()) + using (SignatureHandle sigHandle = tagger.BuildHandle()) { GitOid oid; int res = NativeMethods.git_tag_annotation_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message); @@ -2852,17 +3096,16 @@ public static ObjectId git_tag_annotation_create( } } - public static ObjectId git_tag_create( - RepositorySafeHandle repo, + public static unsafe ObjectId git_tag_create( + RepositoryHandle repo, string name, GitObject target, Signature tagger, string message, bool allowOverwrite) { - using (ThreadAffinity()) using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) - using (SignatureSafeHandle sigHandle = tagger.BuildHandle()) + using (SignatureHandle sigHandle = tagger.BuildHandle()) { GitOid oid; int res = NativeMethods.git_tag_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message, allowOverwrite); @@ -2872,9 +3115,8 @@ public static ObjectId git_tag_create( } } - public static ObjectId git_tag_create_lightweight(RepositorySafeHandle repo, string name, GitObject target, bool allowOverwrite) + public static unsafe ObjectId git_tag_create_lightweight(RepositoryHandle repo, string name, GitObject target, bool allowOverwrite) { - using (ThreadAffinity()) using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) { GitOid oid; @@ -2885,53 +3127,47 @@ public static ObjectId git_tag_create_lightweight(RepositorySafeHandle repo, str } } - public static void git_tag_delete(RepositorySafeHandle repo, string name) + public static unsafe void git_tag_delete(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_tag_delete(repo, name); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_tag_delete(repo, name); + Ensure.ZeroResult(res); } - public static IList git_tag_list(RepositorySafeHandle repo) + public static unsafe IList git_tag_list(RepositoryHandle repo) { - using (ThreadAffinity()) - { - var array = new GitStrArrayNative(); + var array = new GitStrArrayNative(); - try - { - int res = NativeMethods.git_tag_list(out array.Array, repo); - Ensure.ZeroResult(res); + try + { + int res = NativeMethods.git_tag_list(out array.Array, repo); + Ensure.ZeroResult(res); - return array.ReadStrings(); - } - finally - { - array.Dispose(); - } + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static string git_tag_message(GitObjectSafeHandle tag) + public static unsafe string git_tag_message(ObjectHandle tag) { return NativeMethods.git_tag_message(tag); } - public static string git_tag_name(GitObjectSafeHandle tag) + public static unsafe string git_tag_name(ObjectHandle tag) { return NativeMethods.git_tag_name(tag); } - public static Signature git_tag_tagger(GitObjectSafeHandle tag) + public static unsafe Signature git_tag_tagger(ObjectHandle tag) { - IntPtr taggerHandle = NativeMethods.git_tag_tagger(tag); + git_signature* taggerHandle = NativeMethods.git_tag_tagger(tag); // Not all tags have a tagger signature - we need to handle // this case. Signature tagger = null; - if (taggerHandle != IntPtr.Zero) + if (taggerHandle != null) { tagger = new Signature(taggerHandle); } @@ -2939,70 +3175,101 @@ public static Signature git_tag_tagger(GitObjectSafeHandle tag) return tagger; } - public static ObjectId git_tag_target_id(GitObjectSafeHandle tag) + public static unsafe ObjectId git_tag_target_id(ObjectHandle tag) { - return NativeMethods.git_tag_target_id(tag).MarshalAsObjectId(); + return ObjectId.BuildFromPtr(NativeMethods.git_tag_target_id(tag)); } - public static GitObjectType git_tag_target_type(GitObjectSafeHandle tag) + public static unsafe GitObjectType git_tag_target_type(ObjectHandle tag) { return NativeMethods.git_tag_target_type(tag); } #endregion - #region git_transport_ + #region git_trace_ - public static void git_transport_register(String prefix, IntPtr transport_cb, IntPtr param) + /// + /// Install/Enable logging inside of LibGit2 to send messages back to LibGit2Sharp. + /// + /// Since the given callback will be passed into and retained by C code, + /// it is very important that you pass an actual delegate here (and don't + /// let the compiler create/cast a temporary one for you). Furthermore, you + /// must hold a reference to this delegate until you turn off logging. + /// + /// This callback is unlike other callbacks because logging persists in the + /// process until disabled; in contrast, most callbacks are only defined for + /// the duration of the down-call. + /// + public static void git_trace_set(LogLevel level, NativeMethods.git_trace_cb callback) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_transport_register(prefix, transport_cb, param); + int res = NativeMethods.git_trace_set(level, callback); + Ensure.ZeroResult(res); + } - if (res == (int)GitErrorCode.Exists) - { - throw new EntryExistsException(String.Format("A custom transport for '{0}' is already registered", prefix)); - } + #endregion - Ensure.ZeroResult(res); + #region git_transport_ + + public static void git_transport_register(string prefix, IntPtr transport_cb, IntPtr param) + { + int res = NativeMethods.git_transport_register(prefix, transport_cb, param); + + if (res == (int)GitErrorCode.Exists) + { + throw new EntryExistsException("A custom transport for '{0}' is already registered", + prefix); } + + Ensure.ZeroResult(res); } - public static void git_transport_unregister(String prefix) + public static void git_transport_unregister(string prefix) { - using (ThreadAffinity()) + int res = NativeMethods.git_transport_unregister(prefix); + + if (res == (int)GitErrorCode.NotFound) { - int res = NativeMethods.git_transport_unregister(prefix); + throw new NotFoundException("The given transport was not found"); + } - if (res == (int)GitErrorCode.NotFound) - { - throw new NotFoundException("The given transport was not found"); - } + Ensure.ZeroResult(res); + } - Ensure.ZeroResult(res); - } + #endregion + + #region git_transport_smart_ + + public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transport, string user, int methods) + { + return NativeMethods.git_transport_smart_credentials(out cred, transport, user, methods); } #endregion #region git_tree_ - public static Mode git_tree_entry_attributes(SafeHandle entry) + public static unsafe Mode git_tree_entry_attributes(git_tree_entry* entry) { return (Mode)NativeMethods.git_tree_entry_filemode(entry); } - public static TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, long idx) + public static unsafe TreeEntryHandle git_tree_entry_byindex(ObjectHandle tree, long idx) { - return NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + var handle = NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + if (handle == null) + { + return null; + } + + return new TreeEntryHandle(handle, false); } - public static TreeEntrySafeHandle_Owned git_tree_entry_bypath(RepositorySafeHandle repo, ObjectId id, FilePath treeentry_path) + public static unsafe TreeEntryHandle git_tree_entry_bypath(RepositoryHandle repo, ObjectId id, string treeentry_path) { - using (ThreadAffinity()) - using (var obj = new ObjectSafeWrapper(id, repo)) + using (var obj = new ObjectSafeWrapper(id, repo, throwIfMissing: true)) { - TreeEntrySafeHandle_Owned treeEntryPtr; + git_tree_entry* treeEntryPtr; int res = NativeMethods.git_tree_entry_bypath(out treeEntryPtr, obj.ObjectPtr, treeentry_path); if (res == (int)GitErrorCode.NotFound) @@ -3012,31 +3279,26 @@ public static TreeEntrySafeHandle_Owned git_tree_entry_bypath(RepositorySafeHand Ensure.ZeroResult(res); - return treeEntryPtr; + return new TreeEntryHandle(treeEntryPtr, true); } } - public static void git_tree_entry_free(IntPtr treeEntry) + public static unsafe ObjectId git_tree_entry_id(git_tree_entry* entry) { - NativeMethods.git_tree_entry_free(treeEntry); + return ObjectId.BuildFromPtr(NativeMethods.git_tree_entry_id(entry)); } - public static ObjectId git_tree_entry_id(SafeHandle entry) - { - return NativeMethods.git_tree_entry_id(entry).MarshalAsObjectId(); - } - - public static string git_tree_entry_name(SafeHandle entry) + public static unsafe string git_tree_entry_name(git_tree_entry* entry) { return NativeMethods.git_tree_entry_name(entry); } - public static GitObjectType git_tree_entry_type(SafeHandle entry) + public static unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry) { return NativeMethods.git_tree_entry_type(entry); } - public static int git_tree_entrycount(GitObjectSafeHandle tree) + public static unsafe int git_tree_entrycount(ObjectHandle tree) { return (int)NativeMethods.git_tree_entrycount(tree); } @@ -3045,43 +3307,44 @@ public static int git_tree_entrycount(GitObjectSafeHandle tree) #region git_treebuilder_ - public static TreeBuilderSafeHandle git_treebuilder_create() + public static unsafe TreeBuilderHandle git_treebuilder_new(RepositoryHandle repo) { - using (ThreadAffinity()) - { - TreeBuilderSafeHandle builder; - int res = NativeMethods.git_treebuilder_create(out builder, IntPtr.Zero); - Ensure.ZeroResult(res); + git_treebuilder* builder; + int res = NativeMethods.git_treebuilder_new(out builder, repo, IntPtr.Zero); + Ensure.ZeroResult(res); - return builder; - } + return new TreeBuilderHandle(builder, true); } - public static void git_treebuilder_free(IntPtr bld) + public static unsafe void git_treebuilder_insert(TreeBuilderHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) { - NativeMethods.git_treebuilder_free(bld); + GitOid oid = treeEntryDefinition.TargetId.Oid; + int res = NativeMethods.git_treebuilder_insert(IntPtr.Zero, builder, treeentry_name, ref oid, + (uint)treeEntryDefinition.Mode); + Ensure.ZeroResult(res); } - public static void git_treebuilder_insert(TreeBuilderSafeHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) + public static unsafe ObjectId git_treebuilder_write(TreeBuilderHandle bld) { - using (ThreadAffinity()) - { - GitOid oid = treeEntryDefinition.TargetId.Oid; - int res = NativeMethods.git_treebuilder_insert(IntPtr.Zero, builder, treeentry_name, ref oid, (uint)treeEntryDefinition.Mode); - Ensure.ZeroResult(res); - } + GitOid oid; + int res = NativeMethods.git_treebuilder_write(out oid, bld); + Ensure.ZeroResult(res); + + return oid; } - public static ObjectId git_treebuilder_write(RepositorySafeHandle repo, TreeBuilderSafeHandle bld) + #endregion + + #region git_transaction_ + + public static void git_transaction_commit(IntPtr txn) { - using (ThreadAffinity()) - { - GitOid oid; - int res = NativeMethods.git_treebuilder_write(out oid, repo, bld); - Ensure.ZeroResult(res); + NativeMethods.git_transaction_commit(txn); + } - return oid; - } + public static void git_transaction_free(IntPtr txn) + { + NativeMethods.git_transaction_free(txn); } #endregion @@ -3096,222 +3359,490 @@ public static BuiltInFeatures git_libgit2_features() return (BuiltInFeatures)NativeMethods.git_libgit2_features(); } - #endregion + // C# equivalent of libgit2's git_libgit2_opt_t + private enum LibGit2Option + { + GetMWindowSize, // GIT_OPT_GET_MWINDOW_SIZE + SetMWindowSize, // GIT_OPT_SET_MWINDOW_SIZE + GetMWindowMappedLimit, // GIT_OPT_GET_MWINDOW_MAPPED_LIMIT + SetMWindowMappedLimit, // GIT_OPT_SET_MWINDOW_MAPPED_LIMIT + GetSearchPath, // GIT_OPT_GET_SEARCH_PATH + SetSearchPath, // GIT_OPT_SET_SEARCH_PATH + SetCacheObjectLimit, // GIT_OPT_SET_CACHE_OBJECT_LIMIT + SetCacheMaxSize, // GIT_OPT_SET_CACHE_MAX_SIZE + EnableCaching, // GIT_OPT_ENABLE_CACHING + GetCachedMemory, // GIT_OPT_GET_CACHED_MEMORY + GetTemplatePath, // GIT_OPT_GET_TEMPLATE_PATH + SetTemplatePath, // GIT_OPT_SET_TEMPLATE_PATH + SetSslCertLocations, // GIT_OPT_SET_SSL_CERT_LOCATIONS + SetUserAgent, // GIT_OPT_SET_USER_AGENT + EnableStrictObjectCreation, // GIT_OPT_ENABLE_STRICT_OBJECT_CREATION + EnableStrictSymbolicRefCreation, // GIT_OPT_ENABLE_STRICT_SYMBOLIC_REF_CREATION + SetSslCiphers, // GIT_OPT_SET_SSL_CIPHERS + GetUserAgent, // GIT_OPT_GET_USER_AGENT + EnableOfsDelta, // GIT_OPT_ENABLE_OFS_DELTA + EnableFsyncGitdir, // GIT_OPT_ENABLE_FSYNC_GITDIR + GetWindowsSharemode, // GIT_OPT_GET_WINDOWS_SHAREMODE + SetWindowsSharemode, // GIT_OPT_SET_WINDOWS_SHAREMODE + EnableStrictHashVerification, // GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION + SetAllocator, // GIT_OPT_SET_ALLOCATOR, + EnableUnsavedIndexSafety, // GIT_OPT_ENABLE_UNSAVED_INDEX_SAFETY, + GetPackMaxObject, // GIT_OPT_GET_PACK_MAX_OBJECTS, + SetPackMaxObjects, // GIT_OPT_SET_PACK_MAX_OBJECTS, + DisabledPackKeepFileChecks, // GIT_OPT_DISABLE_PACK_KEEP_FILE_CHECKS, + EnableHttpExpectContinue, // GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, + GetMWindowFileLimit, // GIT_OPT_GET_MWINDOW_FILE_LIMIT, + SetMWindowFileLimit, // GIT_OPT_SET_MWINDOW_FILE_LIMIT, + SetOdbPackedPriority, // GIT_OPT_SET_ODB_PACKED_PRIORITY, + SetOdbLoosePriority, // GIT_OPT_SET_ODB_LOOSE_PRIORITY, + GetExtensions, // GIT_OPT_GET_EXTENSIONS, + SetExtensions, // GIT_OPT_SET_EXTENSIONS + GetOwnerValidation, // GIT_OPT_GET_OWNER_VALIDATION + SetOwnerValidation, // GIT_OPT_SET_OWNER_VALIDATION + } - private static ICollection git_foreach( - Func resultSelector, - Func, int> iterator, - params GitErrorCode[] ignoredErrorCodes) + /// + /// Get the paths under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// + /// The paths delimited by 'GIT_PATH_LIST_SEPARATOR'. + /// + public static string git_libgit2_opts_get_search_path(ConfigurationLevel level) { - using (ThreadAffinity()) - { - var result = new List(); - var res = iterator((x, payload) => - { - result.Add(resultSelector(x)); - return 0; - }); - - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } + string path; + using (var buf = new GitBuf()) + { + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.GetSearchPath, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, (uint)level, buf); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetSearchPath, (uint)level, buf); Ensure.ZeroResult(res); - return result; + + path = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; } + + return path; } - private static ICollection git_foreach( - Func resultSelector, - Func, int> iterator, - params GitErrorCode[] ignoredErrorCodes) + public static void git_libgit2_opts_enable_strict_hash_verification(bool enabled) { - using (ThreadAffinity()) - { - var result = new List(); - var res = iterator((x, y, payload) => - { - result.Add(resultSelector(x, y)); - return 0; - }); + if (isOSXArm64) + NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.EnableStrictHashVerification, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, enabled ? 1 : 0); + else + NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableStrictHashVerification, enabled ? 1 : 0); + } - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } + /// + /// Set the path(s) under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// + /// A string of paths delimited by 'GIT_PATH_LIST_SEPARATOR'. + /// Pass null to reset the search path to the default. + /// + public static void git_libgit2_opts_set_search_path(ConfigurationLevel level, string path) + { + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.SetSearchPath, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, (uint)level, path); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetSearchPath, (uint)level, path); + Ensure.ZeroResult(res); + } - Ensure.ZeroResult(res); - return result; - } + /// + /// Enable or disable the libgit2 cache + /// + /// true to enable the cache, false otherwise + public static void git_libgit2_opts_set_enable_caching(bool enabled) + { + // libgit2 expects non-zero value for true + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.EnableCaching, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, enabled ? 1 : 0); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableCaching, enabled ? 1 : 0); + Ensure.ZeroResult(res); } - private static ICollection git_foreach( - Func resultSelector, - Func, int> iterator, - params GitErrorCode[] ignoredErrorCodes) + /// + /// Enable or disable the ofs_delta capabilty + /// + /// true to enable the ofs_delta capabilty, false otherwise + public static void git_libgit2_opts_set_enable_ofsdelta(bool enabled) { - using (ThreadAffinity()) - { - var result = new List(); - var res = iterator((w, x, y, payload) => - { - result.Add(resultSelector(w, x, y)); - return 0; - }); + // libgit2 expects non-zero value for true + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.EnableOfsDelta, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, enabled ? 1 : 0); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableOfsDelta, enabled ? 1 : 0); + Ensure.ZeroResult(res); + } - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } + /// + /// Enable or disable the strict_object_creation capabilty + /// + /// true to enable the strict_object_creation capabilty, false otherwise + public static void git_libgit2_opts_set_enable_strictobjectcreation(bool enabled) + { + // libgit2 expects non-zero value for true + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.EnableStrictObjectCreation, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, enabled ? 1 : 0); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.EnableStrictObjectCreation, enabled ? 1 : 0); + Ensure.ZeroResult(res); + } + /// + /// Sets the user-agent string to be used by the HTTP(S) transport. + /// Note that "git/2.0" will be prepended for compatibility. + /// + /// The user-agent string to use + public static void git_libgit2_opts_set_user_agent(string userAgent) + { + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.SetUserAgent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, userAgent); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetUserAgent, userAgent); + Ensure.ZeroResult(res); + } + + /// + /// Gets the user-agent string used by libgit2. + /// + /// The user-agent string. + /// + /// + public static string git_libgit2_opts_get_user_agent() + { + string userAgent; + + using (var buf = new GitBuf()) + { + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.GetUserAgent, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, buf); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetUserAgent, buf); Ensure.ZeroResult(res); - return result; + + userAgent = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; } - } - public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + return userAgent; + } - private static ICollection git_foreach( - Func resultSelector, - Func, int> iterator, - params GitErrorCode[] ignoredErrorCodes) + public static void git_libgit2_opts_set_extensions(string[] extensions) { - using (ThreadAffinity()) + using (var array = GitStrArrayManaged.BuildFrom(extensions)) { - var result = new List(); - var res = iterator((w, x, y, z, payload) => - { - result.Add(resultSelector(w, x, y, z)); - return 0; - }); + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.SetExtensions, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, array.Array.Strings, array.Array.Count); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetExtensions, array.Array.Strings, array.Array.Count); + Ensure.ZeroResult(res); + } + } - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } + public static string[] git_libgit2_opts_get_extensions() + { + var array = new GitStrArrayNative(); + try + { + int res; + if (isOSXArm64) + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.GetExtensions, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out array.Array); + else + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetExtensions, out array.Array); Ensure.ZeroResult(res); - return result; + + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - private delegate int IteratorNew(out THandle iter); + /// + /// Gets the value of owner validation + /// + public static unsafe bool git_libgit2_opts_get_owner_validation() + { + int res; + int enabled; + + if (isOSXArm64) + { + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.GetOwnerValidation, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, &enabled); + } + else + { + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.GetOwnerValidation, &enabled); + } - private delegate TPayload IteratorNext(TIterator iter, out THandle next, out int res); + Ensure.ZeroResult(res); - private static THandle git_iterator_new(IteratorNew newFunc) - where THandle : SafeHandleBase - { - THandle iter; - Ensure.ZeroResult(newFunc(out iter)); - return iter; + return enabled != 0; } - private static IEnumerable git_iterator_next( - TIterator iter, - IteratorNext nextFunc, - Func resultSelector) - where THandle : SafeHandleBase + /// + /// Enable or disable owner validation + /// + /// true to enable owner validation, false otherwise + public static void git_libgit2_opts_set_owner_validation(bool enabled) { - while (true) + int res; + + if (isOSXArm64) { - var next = default(THandle); - try - { - int res; - var payload = nextFunc(iter, out next, out res); + res = NativeMethods.git_libgit2_opts_osxarm64((int)LibGit2Option.SetOwnerValidation, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, enabled ? 1 : 0); + } + else + { + res = NativeMethods.git_libgit2_opts((int)LibGit2Option.SetOwnerValidation, enabled ? 1 : 0); + } - if (res == (int)GitErrorCode.IterOver) - { - yield break; - } + Ensure.ZeroResult(res); + } + #endregion + #region git_worktree_ + + /// + /// Returns a handle to the corresponding worktree, + /// or an invalid handle if a worktree is not found. + /// + public static unsafe WorktreeHandle git_worktree_lookup(RepositoryHandle repo, string name) + { + git_worktree* worktree; + var res = NativeMethods.git_worktree_lookup(out worktree, repo, name); + + switch (res) + { + case (int)GitErrorCode.Error: + case (int)GitErrorCode.NotFound: + case (int)GitErrorCode.Exists: + case (int)GitErrorCode.OrphanedHead: + return null; + + default: Ensure.ZeroResult(res); - yield return resultSelector(next, payload); - } - finally - { - next.SafeDispose(); - } + return new WorktreeHandle(worktree, true); } } - private static IEnumerable git_iterator( - IteratorNew newFunc, - IteratorNext nextFunc, - Func resultSelector - ) - where TIterator : SafeHandleBase - where THandle : SafeHandleBase + public static unsafe IList git_worktree_list(RepositoryHandle repo) { - using (ThreadAffinity()) + var array = new GitStrArrayNative(); + + try { - using (var iter = git_iterator_new(newFunc)) - { - foreach (var next in git_iterator_next(iter, nextFunc, resultSelector)) - { - yield return next; - } - } + int res = NativeMethods.git_worktree_list(out array.Array, repo); + Ensure.ZeroResult(res); + + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - private static bool RepositoryStateChecker(RepositorySafeHandle repo, Func checker) + public static unsafe RepositoryHandle git_repository_open_from_worktree(WorktreeHandle handle) { - using (ThreadAffinity()) - { - int res = checker(repo); - Ensure.BooleanResult(res); + git_repository* repo; + int res = NativeMethods.git_repository_open_from_worktree(out repo, handle); - return (res == 1); + if (res == (int)GitErrorCode.NotFound) + { + throw new RepositoryNotFoundException("Handle doesn't point at a valid Git repository or workdir."); } + + Ensure.ZeroResult(res); + + return new RepositoryHandle(repo, true); } - private static FilePath ConvertPath(Func pathRetriever) + public static unsafe WorktreeLock git_worktree_is_locked(WorktreeHandle worktree) { - using (ThreadAffinity()) using (var buf = new GitBuf()) { - int result = pathRetriever(buf); + int res = NativeMethods.git_worktree_is_locked(buf, worktree); - if (result == (int)GitErrorCode.NotFound) + if (res < 0) { + // error return null; } - Ensure.ZeroResult(result); - return LaxFilePathMarshaler.FromNative(buf.ptr); + if (res == (int)GitErrorCode.Ok) + { + return new WorktreeLock(); + } + + return new WorktreeLock(true, LaxUtf8Marshaler.FromNative(buf.ptr)); } } - private static Func ThreadAffinity = WithoutThreadAffinity; + public static unsafe bool git_worktree_validate(WorktreeHandle worktree) + { + int res = NativeMethods.git_worktree_validate(worktree); + + return res == (int)GitErrorCode.Ok; + } + + public static unsafe bool git_worktree_unlock(WorktreeHandle worktree) + { + int res = NativeMethods.git_worktree_unlock(worktree); + + return res == (int)GitErrorCode.Ok; + } - internal static void EnableThreadAffinity() + public static unsafe bool git_worktree_lock(WorktreeHandle worktree, string reason) { - ThreadAffinity = WithThreadAffinity; + int res = NativeMethods.git_worktree_lock(worktree, reason); + + return res == (int)GitErrorCode.Ok; } - private static IDisposable WithoutThreadAffinity() + public static unsafe WorktreeHandle git_worktree_add( + RepositoryHandle repo, + string name, + string path, + git_worktree_add_options options) { - return null; + git_worktree* worktree; + int res = NativeMethods.git_worktree_add(out worktree, repo, name, path, options); + Ensure.ZeroResult(res); + return new WorktreeHandle(worktree, true); } - private static IDisposable WithThreadAffinity() + public static unsafe bool git_worktree_prune(WorktreeHandle worktree, + git_worktree_prune_options options) { - return new DisposableThreadAffinityWrapper(); + int res = NativeMethods.git_worktree_prune(worktree, options); + Ensure.ZeroResult(res); + return true; } - private class DisposableThreadAffinityWrapper : IDisposable + #endregion + + private static ICollection git_foreach( + Func resultSelector, + Func, int> iterator, + params GitErrorCode[] ignoredErrorCodes) + { + var result = new List(); + var res = iterator((x, payload) => + { + result.Add(resultSelector(x)); + return 0; + }); + + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) + { + return Array.Empty(); + } + + Ensure.ZeroResult(res); + return result; + } + + private static ICollection git_foreach( + Func resultSelector, + Func, int> iterator, + params GitErrorCode[] ignoredErrorCodes) + { + var result = new List(); + var res = iterator((x, y, payload) => + { + result.Add(resultSelector(x, y)); + return 0; + }); + + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) + { + return Array.Empty(); + } + + Ensure.ZeroResult(res); + return result; + } + + private static ICollection git_foreach( + Func resultSelector, + Func, int> iterator, + params GitErrorCode[] ignoredErrorCodes) + { + var result = new List(); + var res = iterator((w, x, y, payload) => + { + result.Add(resultSelector(w, x, y)); + return 0; + }); + + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) + { + return Array.Empty(); + } + + Ensure.ZeroResult(res); + return result; + } + + public delegate TResult Func(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5); + + private static ICollection git_foreach( + Func resultSelector, + Func, int> iterator, + params GitErrorCode[] ignoredErrorCodes) { - public DisposableThreadAffinityWrapper() + var result = new List(); + var res = iterator((w, x, y, z, payload) => + { + result.Add(resultSelector(w, x, y, z)); + return 0; + }); + + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) { - Thread.BeginThreadAffinity(); + return Array.Empty(); } - public void Dispose() + Ensure.ZeroResult(res); + return result; + } + + private static unsafe bool RepositoryStateChecker(RepositoryHandle repo, Func checker) + { + int res = checker(repo.AsIntPtr()); + Ensure.BooleanResult(res); + + return (res == 1); + } + + private static FilePath ConvertPath(Func pathRetriever) + { + using (var buf = new GitBuf()) { - Thread.EndThreadAffinity(); + int result = pathRetriever(buf); + + if (result == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(result); + return LaxFilePathMarshaler.FromNative(buf.ptr); } } @@ -3336,5 +3867,46 @@ internal static int ConvertResultToCancelFlag(bool result) return result ? 0 : (int)GitErrorCode.User; } } + + /// + /// Class to hold extension methods used by the proxy class. + /// + static class ProxyExtensions + { + /// + /// Convert a UIntPtr to a int value. Will throw + /// exception if there is an overflow. + /// + /// + /// + public static int ConvertToInt(this UIntPtr input) + { + ulong ulongValue = (ulong)input; + if (ulongValue > int.MaxValue) + { + throw new LibGit2SharpException("value exceeds size of an int"); + } + + return (int)input; + } + + + /// + /// Convert a UIntPtr to a long value. Will throw + /// exception if there is an overflow. + /// + /// + /// + public static long ConvertToLong(this UIntPtr input) + { + ulong ulongValue = (ulong)input; + if (ulongValue > long.MaxValue) + { + throw new LibGit2SharpException("value exceeds size of long"); + } + + return (long)input; + } + } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/RawContentStream.cs b/LibGit2Sharp/Core/RawContentStream.cs index d02fe8b81..92b4b3bf0 100644 --- a/LibGit2Sharp/Core/RawContentStream.cs +++ b/LibGit2Sharp/Core/RawContentStream.cs @@ -7,13 +7,13 @@ namespace LibGit2Sharp.Core { internal class RawContentStream : UnmanagedMemoryStream { - private readonly GitObjectSafeHandle handle; + private readonly ObjectHandle handle; private readonly ICollection linkedResources; internal unsafe RawContentStream( - GitObjectSafeHandle handle, - Func bytePtrProvider, - Func sizeProvider, + ObjectHandle handle, + Func bytePtrProvider, + Func sizeProvider, ICollection linkedResources = null) : base((byte*)Wrap(handle, bytePtrProvider, linkedResources).ToPointer(), Wrap(handle, sizeProvider, linkedResources)) @@ -23,8 +23,8 @@ internal unsafe RawContentStream( } private static T Wrap( - GitObjectSafeHandle handle, - Func provider, + ObjectHandle handle, + Func provider, IEnumerable linkedResources) { T value; @@ -43,7 +43,7 @@ private static T Wrap( } private static void Dispose( - GitObjectSafeHandle handle, + ObjectHandle handle, IEnumerable linkedResources) { handle.SafeDispose(); @@ -64,5 +64,5 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); Dispose(handle, linkedResources); } - } + } } diff --git a/LibGit2Sharp/Core/SubmoduleLazyGroup.cs b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs index 0591574d4..42e40e07b 100644 --- a/LibGit2Sharp/Core/SubmoduleLazyGroup.cs +++ b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs @@ -3,7 +3,7 @@ namespace LibGit2Sharp.Core { - internal class SubmoduleLazyGroup : LazyGroup + internal class SubmoduleLazyGroup : LazyGroup { private readonly string name; @@ -13,13 +13,15 @@ public SubmoduleLazyGroup(Repository repo, string name) this.name = name; } - protected override void EvaluateInternal(Action evaluator) + protected override void EvaluateInternal(Action evaluator) { - repo.Submodules.Lookup(name, handle => - { - evaluator(handle); - return default(object); - }, true); + repo.Submodules.Lookup(name, + handle => + { + evaluator(handle); + return default(object); + }, + true); } } } diff --git a/LibGit2Sharp/Core/TarWriter.cs b/LibGit2Sharp/Core/TarWriter.cs index 9f71db44a..0a051b9e6 100644 --- a/LibGit2Sharp/Core/TarWriter.cs +++ b/LibGit2Sharp/Core/TarWriter.cs @@ -75,8 +75,21 @@ public void Write( WriteExtendedHeader(fileNameExtendedHeader, linkExtendedHeader, entrySha, modificationTime); // Note: in case of links, we won't add a content, but the size in the header will still be != 0. It seems strange, but it seem to be what git.git is doing? - WriteHeader(fileNameExtendedHeader.Name, fileNameExtendedHeader.Prefix, modificationTime, (data != null) ? data.Length : 0, mode, - userId, groupId, typeflag, linkExtendedHeader.Link, userName, groupName, deviceMajorNumber, deviceMinorNumber); + WriteHeader(fileNameExtendedHeader.Name, + fileNameExtendedHeader.Prefix, + modificationTime, + (data != null) + ? data.Length + : 0, + mode, + userId, + groupId, + typeflag, + linkExtendedHeader.Link, + userName, + groupName, + deviceMajorNumber, + deviceMinorNumber); // folders have no data, and so do links if (data != null && !isLink) @@ -94,7 +107,9 @@ protected static void WriteContent(long count, Stream data, Stream dest) { int bytesRead = data.Read(buffer, 0, buffer.Length); if (bytesRead < 0) + { throw new IOException("TarWriter unable to read from provided stream"); + } dest.Write(buffer, 0, bytesRead); count -= bytesRead; @@ -103,7 +118,10 @@ protected static void WriteContent(long count, Stream data, Stream dest) { int bytesRead = data.Read(buffer, 0, (int)count); if (bytesRead < 0) + { throw new IOException("TarWriter unable to read from provided stream"); + } + if (bytesRead == 0) { while (count > 0) @@ -113,7 +131,9 @@ protected static void WriteContent(long count, Stream data, Stream dest) } } else + { dest.Write(buffer, 0, bytesRead); + } } } @@ -143,8 +163,19 @@ protected void WriteHeader( string deviceMajorNumber, string deviceMinorNumber) { - var tarHeader = new UsTarHeader(fileName, namePrefix, lastModificationTime, count, mode, - userId, groupId, typeflag, link, userName, groupName, deviceMajorNumber, deviceMinorNumber); + var tarHeader = new UsTarHeader(fileName, + namePrefix, + lastModificationTime, + count, + mode, + userId, + groupId, + typeflag, + link, + userName, + groupName, + deviceMajorNumber, + deviceMinorNumber); var header = tarHeader.GetHeaderValue(); OutStream.Write(header, 0, header.Length); } @@ -168,7 +199,10 @@ private static LinkExtendedHeader ParseLink(bool isLink, Stream data, string ent if (data.Length > 100) { return new LinkExtendedHeader(link, - string.Format(CultureInfo.InvariantCulture, "see %s.paxheader{0}", entrySha), true); + string.Format(CultureInfo.InvariantCulture, + "see %s.paxheader{0}", + entrySha), + true); } return new LinkExtendedHeader(link, link, false); @@ -198,8 +232,20 @@ private void WriteExtendedHeader(FileNameExtendedHeader fileNameExtendedHeader, using (var stream = new MemoryStream(Encoding.ASCII.GetBytes(extHeader))) { - Write(string.Format(CultureInfo.InvariantCulture, "{0}.paxheader", entrySha), stream, modificationTime, "666".OctalToInt32(), - "0", "0", 'x', "root", "root", "0", "0", entrySha, false); + Write(string.Format(CultureInfo.InvariantCulture, + "{0}.paxheader", + entrySha), + stream, modificationTime, + "666".OctalToInt32(), + "0", + "0", + 'x', + "root", + "root", + "0", + "0", + entrySha, + false); } } @@ -208,7 +254,9 @@ private static string BuildKeyValueExtHeader(string key, string value) // "%u %s=%s\n" int len = key.Length + value.Length + 3; for (int i = len; i > 9; i /= 10) + { len++; + } return string.Format(CultureInfo.InvariantCulture, "{0} {1}={2}\n", len, key, value); } @@ -253,38 +301,38 @@ public UsTarHeader( if (userName.Length > 32) { - throw new ArgumentException("ustar userName cannot be longer than 32 characters.", "userName"); + throw new ArgumentException("ustar userName cannot be longer than 32 characters.", nameof(userName)); } if (groupName.Length > 32) { - throw new ArgumentException("ustar groupName cannot be longer than 32 characters.", "groupName"); + throw new ArgumentException("ustar groupName cannot be longer than 32 characters.", nameof(groupName)); } if (userId.Length > 7) { - throw new ArgumentException("ustar userId cannot be longer than 7 characters.", "userId"); + throw new ArgumentException("ustar userId cannot be longer than 7 characters.", nameof(userId)); } if (groupId.Length > 7) { - throw new ArgumentException("ustar groupId cannot be longer than 7 characters.", "groupId"); + throw new ArgumentException("ustar groupId cannot be longer than 7 characters.", nameof(groupId)); } if (deviceMajorNumber.Length > 7) { - throw new ArgumentException("ustar deviceMajorNumber cannot be longer than 7 characters.", "deviceMajorNumber"); + throw new ArgumentException("ustar deviceMajorNumber cannot be longer than 7 characters.", nameof(deviceMajorNumber)); } if (deviceMinorNumber.Length > 7) { - throw new ArgumentException("ustar deviceMinorNumber cannot be longer than 7 characters.", "deviceMinorNumber"); + throw new ArgumentException("ustar deviceMinorNumber cannot be longer than 7 characters.", nameof(deviceMinorNumber)); } if (link.Length > 100) { - throw new ArgumentException("ustar link cannot be longer than 100 characters.", "link"); + throw new ArgumentException("ustar link cannot be longer than 100 characters.", nameof(link)); } #endregion this.mode = Convert.ToString(mode, 8).PadLeft(7, '0'); this.size = size; - unixTime = Convert.ToString(lastModificationTime.ToSecondsSinceEpoch(), 8).PadLeft(11, '0'); + unixTime = Convert.ToString(lastModificationTime.ToUnixTimeSeconds(), 8).PadLeft(11, '0'); this.userId = userId.PadLeft(7, '0'); this.groupId = userId.PadLeft(7, '0'); this.userName = userName; @@ -410,8 +458,12 @@ public static FileNameExtendedHeader Parse(string posixPath, string entrySha) return new FileNameExtendedHeader(posixPath, posixPath.Substring(0, position), posixPath.Substring(position, posixPath.Length - position), false); } - return new FileNameExtendedHeader(posixPath, string.Empty, - string.Format(CultureInfo.InvariantCulture, "{0}.data", entrySha), true); + return new FileNameExtendedHeader(posixPath, + string.Empty, + string.Format(CultureInfo.InvariantCulture, + "{0}.data", + entrySha), + true); } return new FileNameExtendedHeader(posixPath, string.Empty, posixPath, false); diff --git a/LibGit2Sharp/Core/Utf8Marshaler.cs b/LibGit2Sharp/Core/Utf8Marshaler.cs index c623fe99f..54e0086cb 100644 --- a/LibGit2Sharp/Core/Utf8Marshaler.cs +++ b/LibGit2Sharp/Core/Utf8Marshaler.cs @@ -19,7 +19,7 @@ internal class LaxUtf8NoCleanupMarshaler : LaxUtf8Marshaler { private static readonly LaxUtf8NoCleanupMarshaler staticInstance = new LaxUtf8NoCleanupMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -27,8 +27,7 @@ internal class LaxUtf8NoCleanupMarshaler : LaxUtf8Marshaler #region ICustomMarshaler public override void CleanUpNativeData(IntPtr pNativeData) - { - } + { } #endregion } @@ -43,7 +42,7 @@ public override void CleanUpNativeData(IntPtr pNativeData) /// Use this marshaler for function parameters, for example: /// [DllImport(libgit2)] /// internal static extern int git_tag_delete(RepositorySafeHandle repo, - /// [MarshalAs(UnmanagedType.CustomMarshaler, + /// [MarshalAs(UnmanagedType.CustomMarshaler /// MarshalCookie = UniqueId.UniqueIdentifier, /// MarshalTypeRef = typeof(StrictUtf8Marshaler))] String tagName); /// @@ -61,22 +60,23 @@ static StrictUtf8Marshaler() public StrictUtf8Marshaler() : base(encoding) { } - public static ICustomMarshaler GetInstance(String cookie) + public static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } #region ICustomMarshaler - public override Object MarshalNativeToManaged(IntPtr pNativeData) + public override object MarshalNativeToManaged(IntPtr pNativeData) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "{0} cannot be used to retrieve data from libgit2.", GetType().Name)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to retrieve data from libgit2.", + GetType().Name)); } #endregion - public static IntPtr FromManaged(String value) + public static IntPtr FromManaged(string value) { return FromManaged(encoding, value); } @@ -91,12 +91,12 @@ internal class LaxUtf8Marshaler : EncodingMarshaler { private static readonly LaxUtf8Marshaler staticInstance = new LaxUtf8Marshaler(); - private static readonly Encoding encoding = new UTF8Encoding(false, false); + public static readonly Encoding Encoding = new UTF8Encoding(false, false); - public LaxUtf8Marshaler() : base(encoding) + public LaxUtf8Marshaler() : base(Encoding) { } - public static ICustomMarshaler GetInstance(String cookie) + public static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -105,30 +105,36 @@ public static ICustomMarshaler GetInstance(String cookie) public override IntPtr MarshalManagedToNative(object managedObj) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "{0} cannot be used to pass data to libgit2.", GetType().Name)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to pass data to libgit2.", + GetType().Name)); } #endregion + public static unsafe string FromNative(char* pNativeData) + { + return FromNative(Encoding, (byte*)pNativeData); + } + public static string FromNative(IntPtr pNativeData) { - return FromNative(encoding, pNativeData); + return FromNative(Encoding, pNativeData); } public static string FromNative(IntPtr pNativeData, int length) { - return FromNative(encoding, pNativeData, length); + return FromNative(Encoding, pNativeData, length); } public static string FromBuffer(byte[] buffer) { - return FromBuffer(encoding, buffer); + return FromBuffer(Encoding, buffer); } public static string FromBuffer(byte[] buffer, int length) { - return FromBuffer(encoding, buffer, length); + return FromBuffer(Encoding, buffer, length); } } } diff --git a/LibGit2Sharp/Core/WriteStream.cs b/LibGit2Sharp/Core/WriteStream.cs new file mode 100644 index 000000000..6899c67d2 --- /dev/null +++ b/LibGit2Sharp/Core/WriteStream.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; + +namespace LibGit2Sharp.Core +{ + class WriteStream : Stream + { + readonly GitWriteStream nextStream; + readonly IntPtr nextPtr; + + public WriteStream(GitWriteStream nextStream, IntPtr nextPtr) + { + this.nextStream = nextStream; + this.nextPtr = nextPtr; + } + + public override bool CanWrite { get { return true; } } + + public override bool CanRead { get { return false; } } + + public override bool CanSeek { get { return false; } } + + public override long Position + { + get { throw new NotImplementedException(); } + set { throw new InvalidOperationException(); } + } + + public override long Length { get { throw new InvalidOperationException(); } } + + public override void Flush() + { } + + public override void SetLength(long value) + { + throw new InvalidOperationException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new InvalidOperationException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new InvalidOperationException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + int res; + unsafe + { + fixed (byte* bufferPtr = &buffer[offset]) + { + res = nextStream.write(nextPtr, (IntPtr)bufferPtr, (UIntPtr)count); + } + } + + Ensure.Int32Result(res); + } + } +} diff --git a/LibGit2Sharp/Credentials.cs b/LibGit2Sharp/Credentials.cs index a3ad54ab6..50b006b74 100644 --- a/LibGit2Sharp/Credentials.cs +++ b/LibGit2Sharp/Credentials.cs @@ -1,6 +1,4 @@ using System; -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -13,37 +11,7 @@ public abstract class Credentials /// Callback to acquire a credential object. /// /// The newly created credential object. - /// The resource for which we are demanding a credential. - /// The username that was embedded in a "user@host" - /// A bitmask stating which cred types are OK to return. - /// The payload provided when specifying this callback. /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. - protected internal abstract int GitCredentialHandler(out IntPtr cred, IntPtr url, IntPtr usernameFromUrl, GitCredentialType types, IntPtr payload); - } - - internal interface ICredentialsProvider - { - /// - /// Handler to generate for authentication. - /// - CredentialsHandler CredentialsProvider { get; } - } - - internal static class CredentialsProviderExtensions - { - public static CredentialsHandler GetCredentialsHandler(this ICredentialsProvider provider) - { - if (provider == null) - { - return null; - } - - if (provider.CredentialsProvider == null) - { - return null; - } - - return provider.CredentialsProvider; - } + protected internal abstract int GitCredentialHandler(out IntPtr cred); } } diff --git a/LibGit2Sharp/CurrentOperation.cs b/LibGit2Sharp/CurrentOperation.cs index 58f7064ae..9050e8235 100644 --- a/LibGit2Sharp/CurrentOperation.cs +++ b/LibGit2Sharp/CurrentOperation.cs @@ -21,39 +21,49 @@ public enum CurrentOperation /// Revert = 2, + /// + /// A sequencer revert is in progress. + /// + RevertSequence = 3, + /// /// A cherry-pick is in progress. /// - CherryPick = 3, + CherryPick = 4, + + /// + /// A sequencer cherry-pick is in progress. + /// + CherryPickSequence = 5, /// /// A bisect is in progress. /// - Bisect = 4, + Bisect = 6, /// /// A rebase is in progress. /// - Rebase = 5, + Rebase = 7, /// /// A rebase --interactive is in progress. /// - RebaseInteractive = 6, + RebaseInteractive = 8, /// /// A rebase --merge is in progress. /// - RebaseMerge = 7, + RebaseMerge = 9, /// /// A mailbox application (am) is in progress. /// - ApplyMailbox = 8, + ApplyMailbox = 10, /// /// A mailbox application (am) or rebase is in progress. /// - ApplyMailboxOrRebase = 9, + ApplyMailboxOrRebase = 11, } } diff --git a/LibGit2Sharp/CustomDictionary.xml b/LibGit2Sharp/CustomDictionary.xml deleted file mode 100644 index fe603c22b..000000000 --- a/LibGit2Sharp/CustomDictionary.xml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - git - sha - unstage - unstaged - compat - oid - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/LibGit2Sharp/DefaultCredentials.cs b/LibGit2Sharp/DefaultCredentials.cs index 4354bf8ad..b11b4f540 100644 --- a/LibGit2Sharp/DefaultCredentials.cs +++ b/LibGit2Sharp/DefaultCredentials.cs @@ -13,12 +13,8 @@ public sealed class DefaultCredentials : Credentials /// Callback to acquire a credential object. /// /// The newly created credential object. - /// The resource for which we are demanding a credential. - /// The username that was embedded in a "user@host" - /// A bitmask stating which cred types are OK to return. - /// The payload provided when specifying this callback. /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. - protected internal override int GitCredentialHandler(out IntPtr cred, IntPtr url, IntPtr usernameFromUrl, GitCredentialType types, IntPtr payload) + protected internal override int GitCredentialHandler(out IntPtr cred) { return NativeMethods.git_cred_default_new(out cred); } diff --git a/LibGit2Sharp/DescribeOptions.cs b/LibGit2Sharp/DescribeOptions.cs new file mode 100644 index 000000000..3ca6f31eb --- /dev/null +++ b/LibGit2Sharp/DescribeOptions.cs @@ -0,0 +1,68 @@ +namespace LibGit2Sharp +{ + /// + /// Options to define describe behaviour + /// + public sealed class DescribeOptions + { + /// + /// Initializes a new instance of the class. + /// + /// By default: + /// - Only annotated tags will be considered as reference points + /// - The commit id won't be used as a fallback strategy + /// - Only the 10 most recent tags will be considered as candidates to describe the commit + /// - All ancestor lines will be followed upon seeing a merge commit + /// - 7 hexacidemal digits will be used as a minimum commid abbreviated size + /// - Long format will only be used when no direct match has been found + /// + /// + public DescribeOptions() + { + Strategy = DescribeStrategy.Default; + MinimumCommitIdAbbreviatedSize = 7; + OnlyFollowFirstParent = false; + } + + /// + /// The kind of references that will be eligible as reference points. + /// + public DescribeStrategy Strategy { get; set; } + + /// + /// Rather than throwing, should return + /// the abbreviated commit id when the selected + /// didn't identify a proper reference to describe the commit. + /// + public bool UseCommitIdAsFallback { get; set; } + + /// + /// Number of minimum hexadecimal digits used to render a uniquely + /// abbreviated commit id. + /// + public int MinimumCommitIdAbbreviatedSize { get; set; } + + /// + /// Always output the long format (the tag, the number of commits + /// and the abbreviated commit name) even when a direct match has been + /// found. + /// + /// This is useful when one wants to see parts of the commit object + /// name in "describe" output, even when the commit in question happens + /// to be a tagged version. Instead of just emitting the tag name, it + /// will describe such a commit as v1.2-0-gdeadbee (0th commit since + /// tag v1.2 that points at object deadbee...). + /// + /// + public bool AlwaysRenderLongFormat { get; set; } + + /// + /// Follow only the first parent commit upon seeing a merge commit. + /// + /// This is useful when you wish to not match tags on branches merged in + /// the history of the target commit. + /// + /// + public bool OnlyFollowFirstParent { get; set; } + } +} diff --git a/LibGit2Sharp/DescribeStrategy.cs b/LibGit2Sharp/DescribeStrategy.cs new file mode 100644 index 000000000..dbeccc7c2 --- /dev/null +++ b/LibGit2Sharp/DescribeStrategy.cs @@ -0,0 +1,30 @@ +namespace LibGit2Sharp +{ + /// + /// Specify the kind of committish which will be considered + /// when trying to identify the closest reference to the described commit. + /// + public enum DescribeStrategy + { + /// + /// Only consider annotated tags. + /// + Default = 0, + + /// + /// Consider both annotated and lightweight tags. + /// + /// This will match every reference under the refs/tags/ namespace. + /// + /// + Tags, + + /// + /// Consider annotated and lightweight tags, local and remote tracking branches. + /// + /// This will match every reference under the refs/ namespace. + /// + /// + All, + } +} diff --git a/LibGit2Sharp/DetachedHead.cs b/LibGit2Sharp/DetachedHead.cs index 094d9d263..d934db2c4 100644 --- a/LibGit2Sharp/DetachedHead.cs +++ b/LibGit2Sharp/DetachedHead.cs @@ -4,8 +4,7 @@ internal class DetachedHead : Branch { internal DetachedHead(Repository repo, Reference reference) : base(repo, reference, "(no branch)") - { - } + { } protected override string Shorten() { diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index a1e87e3b3..857eb8ed1 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Text; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; -using Environment = System.Environment; namespace LibGit2Sharp { @@ -50,11 +48,25 @@ private static GitDiffOptions BuildOptions(DiffModifiers diffOptions, FilePath[] options.Flags |= GitDiffOptionFlags.GIT_DIFF_INCLUDE_UNMODIFIED; } + if (compareOptions.Algorithm == DiffAlgorithm.Patience) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_PATIENCE; + } + else if (compareOptions.Algorithm == DiffAlgorithm.Minimal) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_MINIMAL; + } + if (diffOptions.HasFlag(DiffModifiers.DisablePathspecMatch)) { options.Flags |= GitDiffOptionFlags.GIT_DIFF_DISABLE_PATHSPEC_MATCH; } + if (compareOptions.IndentHeuristic) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_INDENT_HEURISTIC; + } + if (matchedPathsAggregator != null) { options.NotifyCallback = matchedPathsAggregator.OnGitDiffNotify; @@ -91,13 +103,38 @@ private static IDictionary> ChangesBuilders = new Dictionary> + private static readonly IDictionary> ChangesBuilders = new Dictionary> { { typeof(Patch), diff => new Patch(diff) }, { typeof(TreeChanges), diff => new TreeChanges(diff) }, { typeof(PatchStats), diff => new PatchStats(diff) }, }; + + private static T BuildDiffResult(DiffHandle diff) where T : class, IDiffResult + { + Func builder; + + if (!ChangesBuilders.TryGetValue(typeof(T), out builder)) + { + throw new LibGit2SharpException("User-defined types passed to Compare are not supported. Supported values are: {0}", + string.Join(", ", ChangesBuilders.Keys.Select(x => x.Name))); + } + + return (T)builder(diff); + } + + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// A containing the changes between the and the . + public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob) + { + return Compare(oldBlob, newBlob, null); + } + /// /// Show changes between two s. /// @@ -105,7 +142,7 @@ private static IDictionaryThe you want to compare to. /// Additional options to define comparison behavior. /// A containing the changes between the and the . - public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob, CompareOptions compareOptions = null) + public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob, CompareOptions compareOptions) { using (GitDiffOptions options = BuildOptions(DiffModifiers.None, compareOptions: compareOptions)) { @@ -113,6 +150,29 @@ public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob, CompareOptions } } + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree) where T : class, IDiffResult + { + return Compare(oldTree, newTree, null, null, null); + } + + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// The list of paths (either files or directories) that should be compared. + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths) where T : class, IDiffResult + { + return Compare(oldTree, newTree, paths, null, null); + } + /// /// Show changes between two s. /// @@ -123,20 +183,53 @@ public virtual ContentChanges Compare(Blob oldBlob, Blob newBlob, CompareOptions /// If set, the passed will be treated as explicit paths. /// Use these options to determine how unmatched explicit paths should be handled. /// + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult + { + return Compare(oldTree, newTree, paths, explicitPathsOptions, null); + } + + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// The list of paths (either files or directories) that should be compared. /// Additional options to define patch generation behavior. /// A containing the changes between the and the . - public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths = null, ExplicitPathsOptions explicitPathsOptions = null, - CompareOptions compareOptions = null) where T : class + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, CompareOptions compareOptions) where T : class, IDiffResult { - Func builder; + return Compare(oldTree, newTree, paths, null, compareOptions); + } - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// Additional options to define patch generation behavior. + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree, CompareOptions compareOptions) where T : class, IDiffResult + { + return Compare(oldTree, newTree, null, null, compareOptions); + } + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The you want to compare to. + /// The list of paths (either files or directories) that should be compared. + /// + /// If set, the passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + /// Additional options to define patch generation behavior. + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions, + CompareOptions compareOptions) where T : class, IDiffResult + { var comparer = TreeToTree(repo); ObjectId oldTreeId = oldTree != null ? oldTree.Id : null; ObjectId newTreeId = newTree != null ? newTree.Id : null; @@ -146,18 +239,58 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path { diffOptions |= DiffModifiers.DisablePathspecMatch; - if (explicitPathsOptions.ShouldFailOnUnmatchedPath || - explicitPathsOptions.OnUnmatchedPath != null) + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) { diffOptions |= DiffModifiers.IncludeUnmodified; } } - using (DiffSafeHandle diff = BuildDiffList(oldTreeId, newTreeId, comparer, - diffOptions, paths, explicitPathsOptions, compareOptions)) + DiffHandle diff = BuildDiffList(oldTreeId, newTreeId, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try { - return (T)builder(diff); + return BuildDiffResult(diff); } + catch + { + diff.SafeDispose(); + throw; + } + } + + /// + /// Show changes between a and the Index, the Working Directory, or both. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The to compare from. + /// The targets to compare to. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the and the selected target. + public virtual T Compare(Tree oldTree, DiffTargets diffTargets) where T : class, IDiffResult + { + return Compare(oldTree, diffTargets, null, null, null); + } + + /// + /// Show changes between a and the Index, the Working Directory, or both. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The to compare from. + /// The targets to compare to. + /// The list of paths (either files or directories) that should be compared. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the and the selected target. + public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths) where T : class, IDiffResult + { + return Compare(oldTree, diffTargets, paths, null, null); } /// @@ -174,22 +307,36 @@ public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable path /// If set, the passed will be treated as explicit paths. /// Use these options to determine how unmatched explicit paths should be handled. /// - /// Additional options to define patch generation behavior. /// Can be either a if you are only interested in the list of files modified, added, ..., or /// a if you want the actual patch content for the whole diff and for individual files. /// A containing the changes between the and the selected target. - public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths = null, - ExplicitPathsOptions explicitPathsOptions = null, CompareOptions compareOptions = null) where T : class + public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { - Func builder; - - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } + return Compare(oldTree, diffTargets, paths, explicitPathsOptions, null); + } + /// + /// Show changes between a and the Index, the Working Directory, or both. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The to compare from. + /// The targets to compare to. + /// The list of paths (either files or directories) that should be compared. + /// + /// If set, the passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + /// Additional options to define patch generation behavior. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the and the selected target. + public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions, CompareOptions compareOptions) where T : class, IDiffResult + { var comparer = HandleRetrieverDispatcher[diffTargets](repo); ObjectId oldTreeId = oldTree != null ? oldTree.Id : null; @@ -201,20 +348,73 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable(diff); + } + catch + { + diff.SafeDispose(); + throw; } } + /// + /// Show changes between the working directory and the index. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the working directory and the index. + public virtual T Compare() where T : class, IDiffResult + { + return Compare(DiffModifiers.None); + } + + /// + /// Show changes between the working directory and the index. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The list of paths (either files or directories) that should be compared. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the working directory and the index. + public virtual T Compare(IEnumerable paths) where T : class, IDiffResult + { + return Compare(DiffModifiers.None, paths); + } + + /// + /// Show changes between the working directory and the index. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The list of paths (either files or directories) that should be compared. + /// If true, include untracked files from the working dir as additions. Otherwise ignore them. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the working directory and the index. + public virtual T Compare(IEnumerable paths, bool includeUntracked) where T : class, IDiffResult + { + return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths); + } + /// /// Show changes between the working directory and the index. /// @@ -228,49 +428,72 @@ public virtual T Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable will be treated as explicit paths. /// Use these options to determine how unmatched explicit paths should be handled. /// - /// Additional options to define patch generation behavior. /// Can be either a if you are only interested in the list of files modified, added, ..., or /// a if you want the actual patch content for the whole diff and for individual files. /// A containing the changes between the working directory and the index. - public virtual T Compare(IEnumerable paths = null, bool includeUntracked = false, ExplicitPathsOptions explicitPathsOptions = null, - CompareOptions compareOptions = null) where T : class + public virtual T Compare(IEnumerable paths, bool includeUntracked, ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions); } - internal virtual T Compare(DiffModifiers diffOptions, IEnumerable paths = null, - ExplicitPathsOptions explicitPathsOptions = null, CompareOptions compareOptions = null) where T : class + /// + /// Show changes between the working directory and the index. + /// + /// The level of diff performed can be specified by passing either a + /// or type as the generic parameter. + /// + /// + /// The list of paths (either files or directories) that should be compared. + /// If true, include untracked files from the working dir as additions. Otherwise ignore them. + /// + /// If set, the passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + /// Additional options to define patch generation behavior. + /// Can be either a if you are only interested in the list of files modified, added, ..., or + /// a if you want the actual patch content for the whole diff and for individual files. + /// A containing the changes between the working directory and the index. + public virtual T Compare( + IEnumerable paths, + bool includeUntracked, + ExplicitPathsOptions explicitPathsOptions, + CompareOptions compareOptions) where T : class, IDiffResult { - Func builder; - - if (!ChangesBuilders.TryGetValue(typeof (T), out builder)) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, - "Unexpected type '{0}' passed to Compare. Supported values are either '{1}' or '{2}'.", typeof (T), - typeof (TreeChanges), typeof (Patch))); - } + return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions, compareOptions); + } + internal virtual T Compare( + DiffModifiers diffOptions, + IEnumerable paths = null, + ExplicitPathsOptions explicitPathsOptions = null, + CompareOptions compareOptions = null) where T : class, IDiffResult + { var comparer = WorkdirToIndex(repo); if (explicitPathsOptions != null) { diffOptions |= DiffModifiers.DisablePathspecMatch; - if (explicitPathsOptions.ShouldFailOnUnmatchedPath || - explicitPathsOptions.OnUnmatchedPath != null) + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) { diffOptions |= DiffModifiers.IncludeUnmodified; } } - using (DiffSafeHandle diff = BuildDiffList(null, null, comparer, - diffOptions, paths, explicitPathsOptions, compareOptions)) + DiffHandle diff = BuildDiffList(null, null, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try + { + return BuildDiffResult(diff); + } + catch { - return (T)builder(diff); + diff.SafeDispose(); + throw; } } - internal delegate DiffSafeHandle TreeComparisonHandleRetriever(ObjectId oldTreeId, ObjectId newTreeId, GitDiffOptions options); + internal delegate DiffHandle TreeComparisonHandleRetriever(ObjectId oldTreeId, ObjectId newTreeId, GitDiffOptions options); private static TreeComparisonHandleRetriever TreeToTree(Repository repo) { @@ -291,9 +514,9 @@ private static TreeComparisonHandleRetriever WorkdirAndIndexToTree(Repository re { TreeComparisonHandleRetriever comparisonHandleRetriever = (oh, nh, o) => { - DiffSafeHandle diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); + DiffHandle diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); - using (DiffSafeHandle diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o)) + using (DiffHandle diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o)) { Proxy.git_diff_merge(diff, diff2); } @@ -309,20 +532,44 @@ private static TreeComparisonHandleRetriever IndexToTree(Repository repo) return (oh, nh, o) => Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); } - private DiffSafeHandle BuildDiffList(ObjectId oldTreeId, ObjectId newTreeId, TreeComparisonHandleRetriever comparisonHandleRetriever, - DiffModifiers diffOptions, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions, + private DiffHandle BuildDiffList( + ObjectId oldTreeId, + ObjectId newTreeId, + TreeComparisonHandleRetriever comparisonHandleRetriever, + DiffModifiers diffOptions, + IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions, CompareOptions compareOptions) { - var matchedPaths = new MatchedPathsAggregator(); var filePaths = repo.ToFilePaths(paths); + MatchedPathsAggregator matchedPaths = null; + + // We can't match paths unless we've got something to match + // against and we're told to do so. + if (filePaths != null && explicitPathsOptions != null) + { + if (explicitPathsOptions.OnUnmatchedPath != null || explicitPathsOptions.ShouldFailOnUnmatchedPath) + { + matchedPaths = new MatchedPathsAggregator(); + } + } + using (GitDiffOptions options = BuildOptions(diffOptions, filePaths, matchedPaths, compareOptions)) { var diffList = comparisonHandleRetriever(oldTreeId, newTreeId, options); - if (explicitPathsOptions != null) + if (matchedPaths != null) { - DispatchUnmatchedPaths(explicitPathsOptions, filePaths, matchedPaths); + try + { + DispatchUnmatchedPaths(explicitPathsOptions, filePaths, matchedPaths); + } + catch + { + diffList.Dispose(); + throw; + } } DetectRenames(diffList, compareOptions); @@ -331,11 +578,10 @@ private DiffSafeHandle BuildDiffList(ObjectId oldTreeId, ObjectId newTreeId, Tre } } - private static void DetectRenames(DiffSafeHandle diffList, CompareOptions compareOptions) + private static void DetectRenames(DiffHandle diffList, CompareOptions compareOptions) { var similarityOptions = (compareOptions == null) ? null : compareOptions.Similarity; - if (similarityOptions == null || - similarityOptions.RenameDetectionMode == RenameDetectionMode.Default) + if (similarityOptions == null || similarityOptions.RenameDetectionMode == RenameDetectionMode.Default) { Proxy.git_diff_find_similar(diffList, null); return; @@ -398,14 +644,15 @@ private static void DetectRenames(DiffSafeHandle diffList, CompareOptions compar Proxy.git_diff_find_similar(diffList, opts); } - private static void DispatchUnmatchedPaths(ExplicitPathsOptions explicitPathsOptions, - IEnumerable filePaths, - IEnumerable matchedPaths) + private static void DispatchUnmatchedPaths( + ExplicitPathsOptions explicitPathsOptions, + IEnumerable filePaths, + IEnumerable matchedPaths) { List unmatchedPaths = (filePaths != null ? filePaths.Except(matchedPaths) : Enumerable.Empty()).ToList(); - if (!unmatchedPaths.Any()) + if (unmatchedPaths.Count == 0) { return; } diff --git a/LibGit2Sharp/DiffAlgorithm.cs b/LibGit2Sharp/DiffAlgorithm.cs new file mode 100644 index 000000000..07370e5a9 --- /dev/null +++ b/LibGit2Sharp/DiffAlgorithm.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// Algorithm used when performing a Diff. + /// + public enum DiffAlgorithm + { + /// + /// The basic greedy diff algorithm. + /// + Myers = 0, + + /// + /// Use "minimal diff" algorithm when generating patches. + /// + Minimal = 1, + + /// + /// Use "patience diff" algorithm when generating patches. + /// + Patience = 2, + } +} diff --git a/LibGit2Sharp/DiffTargets.cs b/LibGit2Sharp/DiffTargets.cs index 58e3f2f4d..40203ee60 100644 --- a/LibGit2Sharp/DiffTargets.cs +++ b/LibGit2Sharp/DiffTargets.cs @@ -18,4 +18,4 @@ public enum DiffTargets /// WorkingDirectory = 2, } -} \ No newline at end of file +} diff --git a/LibGit2Sharp/DirectReference.cs b/LibGit2Sharp/DirectReference.cs index 00de258a6..b9cc304b3 100644 --- a/LibGit2Sharp/DirectReference.cs +++ b/LibGit2Sharp/DirectReference.cs @@ -18,12 +18,21 @@ protected DirectReference() internal DirectReference(string canonicalName, IRepository repo, ObjectId targetId) : base(repo, canonicalName, targetId.Sha) { - targetBuilder = new Lazy(() => repo.Lookup(targetId)); + targetBuilder = new Lazy(() => + { + if (repo == null) + { + throw new InvalidOperationException("Target requires a local repository"); + } + + return repo.Lookup(targetId); + }); } /// /// Gets the target of this /// + /// Throws if Local Repository is not set. public virtual GitObject Target { get { return targetBuilder.Value; } diff --git a/LibGit2Sharp/EmptyCommitException.cs b/LibGit2Sharp/EmptyCommitException.cs index aa54a5f3c..00d1081e5 100644 --- a/LibGit2Sharp/EmptyCommitException.cs +++ b/LibGit2Sharp/EmptyCommitException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif namespace LibGit2Sharp { @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// The exception that is thrown when a commit would create an "empty" /// commit that is treesame to its parent without an explicit override. /// +#if NETFRAMEWORK [Serializable] +#endif public class EmptyCommitException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public EmptyCommitException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public EmptyCommitException() /// A message that describes the error. public EmptyCommitException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public EmptyCommitException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public EmptyCommitException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public EmptyCommitException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +54,7 @@ public EmptyCommitException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected EmptyCommitException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/EntryExistsException.cs b/LibGit2Sharp/EntryExistsException.cs index 9dff4d750..3ebfbdfba 100644 --- a/LibGit2Sharp/EntryExistsException.cs +++ b/LibGit2Sharp/EntryExistsException.cs @@ -1,5 +1,8 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif + using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,15 +10,16 @@ namespace LibGit2Sharp /// /// The exception that is thrown attempting to create a resource that already exists. /// +#if NETFRAMEWORK [Serializable] +#endif public class EntryExistsException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public EntryExistsException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +27,16 @@ public EntryExistsException() /// A message that describes the error. public EntryExistsException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public EntryExistsException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +45,9 @@ public EntryExistsException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public EntryExistsException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +55,11 @@ public EntryExistsException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected EntryExistsException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif internal EntryExistsException(string message, GitErrorCode code, GitErrorCategory category) : base(message, code, category) - { - } + { } } } diff --git a/LibGit2Sharp/FetchHead.cs b/LibGit2Sharp/FetchHead.cs index 75f12ae01..812865cf3 100644 --- a/LibGit2Sharp/FetchHead.cs +++ b/LibGit2Sharp/FetchHead.cs @@ -15,11 +15,20 @@ internal class FetchHead : ReferenceWrapper protected FetchHead() { } - internal FetchHead(Repository repo, string remoteCanonicalName, - string url, ObjectId targetId, bool forMerge, int index) - : base(repo, new DirectReference( - string.Format(CultureInfo.InvariantCulture, "FETCH_HEAD[{0}]", index), - repo, targetId), r => r.CanonicalName) + internal FetchHead( + Repository repo, + string remoteCanonicalName, + string url, + ObjectId targetId, + bool forMerge, + int index) + : base(repo, + new DirectReference(string.Format(CultureInfo.InvariantCulture, + "FETCH_HEAD[{0}]", + index), + repo, + targetId), + r => r.CanonicalName) { Url = url; ForMerge = forMerge; @@ -52,7 +61,7 @@ public virtual GitObject Target /// The URL of the remote repository this /// has been built from. /// - public virtual String Url { get; private set; } + public virtual string Url { get; private set; } /// /// Determines if this fetch head entry has been explicitly fetched. diff --git a/LibGit2Sharp/FetchOptions.cs b/LibGit2Sharp/FetchOptions.cs index 40af355c3..6f354a5d5 100644 --- a/LibGit2Sharp/FetchOptions.cs +++ b/LibGit2Sharp/FetchOptions.cs @@ -1,12 +1,9 @@ -using System; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Collection of parameters controlling Fetch behavior. /// - public sealed class FetchOptions : ICredentialsProvider + public sealed class FetchOptions : FetchOptionsBase { /// /// Specifies the tag-following behavior of the fetch operation. @@ -21,27 +18,41 @@ public sealed class FetchOptions : ICredentialsProvider public TagFetchMode? TagFetchMode { get; set; } /// - /// Delegate that progress updates of the network transfer portion of fetch - /// will be reported through. - /// - public ProgressHandler OnProgress { get; set; } - - /// - /// Delegate that updates of remote tracking branches will be reported through. + /// Specifies the pruning behaviour for the fetch operation + /// + /// If not set, the configuration's setting will take effect. If true, the branches which no longer + /// exist on the remote will be removed from the remote-tracking branches. + /// /// - public UpdateTipsHandler OnUpdateTips { get; set; } + public bool? Prune { get; set; } /// - /// Callback method that transfer progress will be reported through. + /// Specifies the depth of the fetch to perform. /// - /// Reports the client's state regarding the received and processed (bytes, objects) from the server. + /// Default value is 0 (full fetch). /// /// - public TransferProgressHandler OnTransferProgress { get; set; } + public int Depth { get; set; } = 0; /// - /// Handler to generate for authentication. + /// Get/Set the custom headers. + /// + /// + /// This allows you to set custom headers (e.g. X-Forwarded-For, + /// X-Request-Id, etc), + /// /// - public CredentialsHandler CredentialsProvider { get; set; } + /// + /// Libgit2 sets some headers for HTTP requests (User-Agent, Host, + /// Accept, Content-Type, Transfer-Encoding, Content-Length, Accept) that + /// cannot be overriden. + /// + /// + /// var fetchOptions - new FetchOptions() { + /// CustomHeaders = new String[] {"X-Request-Id: 12345"} + /// }; + /// + /// The custom headers string array + public string[] CustomHeaders { get; set; } } } diff --git a/LibGit2Sharp/FetchOptionsBase.cs b/LibGit2Sharp/FetchOptionsBase.cs new file mode 100644 index 000000000..0e548652f --- /dev/null +++ b/LibGit2Sharp/FetchOptionsBase.cs @@ -0,0 +1,57 @@ +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Base collection of parameters controlling Fetch behavior. + /// + public abstract class FetchOptionsBase + { + internal FetchOptionsBase() + { } + + /// + /// Handler for network transfer and indexing progress information. + /// + public ProgressHandler OnProgress { get; set; } + + /// + /// Handler for updates to remote tracking branches. + /// + public UpdateTipsHandler OnUpdateTips { get; set; } + + /// + /// Handler for data transfer progress. + /// + /// Reports the client's state regarding the received and processed (bytes, objects) from the server. + /// + /// + public TransferProgressHandler OnTransferProgress { get; set; } + + /// + /// Handler to generate for authentication. + /// + public CredentialsHandler CredentialsProvider { get; set; } + + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to proceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + + /// + /// Starting to operate on a new repository. + /// + public RepositoryOperationStarting RepositoryOperationStarting { get; set; } + + /// + /// Completed operating on the current repository. + /// + public RepositoryOperationCompleted RepositoryOperationCompleted { get; set; } + + /// + /// Options for connecting through a proxy. + /// + public ProxyOptions ProxyOptions { get; } = new(); + } +} diff --git a/LibGit2Sharp/FileStatus.cs b/LibGit2Sharp/FileStatus.cs index 68e41b7b5..fbd32affd 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -21,17 +21,17 @@ public enum FileStatus /// /// New file has been added to the Index. It's unknown from the Head. /// - Added = (1 << 0), /* GIT_STATUS_INDEX_NEW */ + NewInIndex = (1 << 0), /* GIT_STATUS_INDEX_NEW */ /// /// New version of a file has been added to the Index. A previous version exists in the Head. /// - Staged = (1 << 1), /* GIT_STATUS_INDEX_MODIFIED */ + ModifiedInIndex = (1 << 1), /* GIT_STATUS_INDEX_MODIFIED */ /// /// The deletion of a file has been promoted from the working directory to the Index. A previous version exists in the Head. /// - Removed = (1 << 2), /* GIT_STATUS_INDEX_DELETED */ + DeletedFromIndex = (1 << 2), /* GIT_STATUS_INDEX_DELETED */ /// /// The renaming of a file has been promoted from the working directory to the Index. A previous version exists in the Head. @@ -41,32 +41,32 @@ public enum FileStatus /// /// A change in type for a file has been promoted from the working directory to the Index. A previous version exists in the Head. /// - StagedTypeChange = (1 << 4), /* GIT_STATUS_INDEX_TYPECHANGE */ + TypeChangeInIndex = (1 << 4), /* GIT_STATUS_INDEX_TYPECHANGE */ /// /// New file in the working directory, unknown from the Index and the Head. /// - Untracked = (1 << 7), /* GIT_STATUS_WT_NEW */ + NewInWorkdir = (1 << 7), /* GIT_STATUS_WT_NEW */ /// /// The file has been updated in the working directory. A previous version exists in the Index. /// - Modified = (1 << 8), /* GIT_STATUS_WT_MODIFIED */ + ModifiedInWorkdir = (1 << 8), /* GIT_STATUS_WT_MODIFIED */ /// /// The file has been deleted from the working directory. A previous version exists in the Index. /// - Missing = (1 << 9), /* GIT_STATUS_WT_DELETED */ + DeletedFromWorkdir = (1 << 9), /* GIT_STATUS_WT_DELETED */ /// /// The file type has been changed in the working directory. A previous version exists in the Index. /// - TypeChanged = (1 << 10), /* GIT_STATUS_WT_TYPECHANGE */ + TypeChangeInWorkdir = (1 << 10), /* GIT_STATUS_WT_TYPECHANGE */ /// /// The file has been renamed in the working directory. The previous version at the previous name exists in the Index. /// - RenamedInWorkDir = (1 << 11), /* GIT_STATUS_WT_RENAMED */ + RenamedInWorkdir = (1 << 11), /* GIT_STATUS_WT_RENAMED */ /// /// The file is unreadable in the working directory. @@ -74,8 +74,13 @@ public enum FileStatus Unreadable = (1 << 12), /* GIT_STATUS_WT_UNREADABLE */ /// - /// The file is but its name and/or path matches an exclude pattern in a gitignore file. + /// The file is but its name and/or path matches an exclude pattern in a gitignore file. /// Ignored = (1 << 14), /* GIT_STATUS_IGNORED */ + + /// + /// The file is due to a merge. + /// + Conflicted = (1 << 15), /* GIT_STATUS_CONFLICTED */ } } diff --git a/LibGit2Sharp/Filter.cs b/LibGit2Sharp/Filter.cs new file mode 100644 index 000000000..0ab999f19 --- /dev/null +++ b/LibGit2Sharp/Filter.cs @@ -0,0 +1,403 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A filter is a way to execute code against a file as it moves to and from the git + /// repository and into the working directory. + /// + public abstract class Filter : IEquatable + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.Name, x => x.Attributes); + // 64K is optimal buffer size per https://technet.microsoft.com/en-us/library/cc938632.aspx + private const int BufferSize = 64 * 1024; + + /// + /// Initializes a new instance of the class. + /// And allocates the filter natively. + /// The unique name with which this filtered is registered with + /// A list of attributes which this filter applies to + /// + protected Filter(string name, IEnumerable attributes) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(attributes, "attributes"); + + this.name = name; + this.attributes = attributes; + var attributesAsString = string.Join(",", this.attributes.Select(attr => attr.FilterDefinition)); + + gitFilter = new GitFilter + { + attributes = EncodingMarshaler.FromManaged(Encoding.UTF8, attributesAsString), + init = InitializeCallback, + stream = StreamCreateCallback, + }; + } + /// + /// Finalizer called by the , deregisters and frees native memory associated with the registered filter in libgit2. + /// + ~Filter() + { + GlobalSettings.DeregisterFilter(this); + +#if LEAKS_IDENTIFYING + int activeStreamCount = activeStreams.Count; + if (activeStreamCount > 0) + { + Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0} leaked {1} stream handles at finalization", GetType().Name, activeStreamCount)); + } +#endif + } + + private readonly string name; + private readonly IEnumerable attributes; + private readonly GitFilter gitFilter; + private readonly ConcurrentDictionary activeStreams = new ConcurrentDictionary(); + + /// + /// State bag used to keep necessary reference from being + /// garbage collected during filter processing. + /// + private class StreamState + { + public GitWriteStream thisStream; + public GitWriteStream nextStream; + public IntPtr thisPtr; + public IntPtr nextPtr; + public FilterSource filterSource; + public Stream output; + } + + /// + /// The name that this filter was registered with + /// + public string Name + { + get { return name; } + } + + /// + /// The filter filterForAttributes. + /// + public IEnumerable Attributes + { + get { return attributes; } + } + + /// + /// The marshalled filter + /// + internal GitFilter GitFilter + { + get { return gitFilter; } + } + + /// + /// Complete callback on filter + /// + /// This optional callback will be invoked when the upstream filter is + /// closed. Gives the filter a chance to perform any final actions or + /// necissary clean up. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Output to the downstream filter or output writer + protected virtual void Complete(string path, string root, Stream output) + { } + + /// + /// Initialize callback on filter + /// + /// Specified as `filter.initialize`, this is an optional callback invoked + /// before a filter is first used. It will be called once at most. + /// + /// If non-NULL, the filter's `initialize` callback will be invoked right + /// before the first use of the filter, so you can defer expensive + /// initialization operations (in case the library is being used in a way + /// that doesn't need the filter. + /// + protected virtual void Initialize() + { } + + /// + /// Indicates that a filter is going to be applied for the given file for + /// the given mode. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// The filter mode + protected virtual void Create(string path, string root, FilterMode mode) + { } + + /// + /// Clean the input stream and write to the output stream. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Input from the upstream filter or input reader + /// Output to the downstream filter or output writer + protected virtual void Clean(string path, string root, Stream input, Stream output) + { + input.CopyTo(output); + } + + /// + /// Smudge the input stream and write to the output stream. + /// + /// The path of the file being filtered + /// The path of the working directory for the owning repository + /// Input from the upstream filter or input reader + /// Output to the downstream filter or output writer + protected virtual void Smudge(string path, string root, Stream input, Stream output) + { + input.CopyTo(output); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public override bool Equals(object obj) + { + return Equals(obj as Filter); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public bool Equals(Filter other) + { + return equalityHelper.Equals(this, other); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return equalityHelper.GetHashCode(this); + } + + /// + /// Tests if two are equal. + /// + /// First to compare. + /// Second to compare. + /// True if the two objects are equal; false otherwise. + public static bool operator ==(Filter left, Filter right) + { + return Equals(left, right); + } + + /// + /// Tests if two are different. + /// + /// First to compare. + /// Second to compare. + /// True if the two objects are different; false otherwise. + public static bool operator !=(Filter left, Filter right) + { + return !Equals(left, right); + } + + /// + /// Initialize callback on filter + /// + /// Specified as `filter.initialize`, this is an optional callback invoked + /// before a filter is first used. It will be called once at most. + /// + /// If non-NULL, the filter's `initialize` callback will be invoked right + /// 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). + /// + int InitializeCallback(IntPtr filterPointer) + { + int result = 0; + try + { + Initialize(); + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.InitializeCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + return result; + } + + int StreamCreateCallback(out IntPtr git_writestream_out, GitFilter self, IntPtr payload, IntPtr filterSourcePtr, IntPtr git_writestream_next) + { + int result = 0; + var state = new StreamState(); + + try + { + Ensure.ArgumentNotZeroIntPtr(filterSourcePtr, "filterSourcePtr"); + Ensure.ArgumentNotZeroIntPtr(git_writestream_next, "git_writestream_next"); + + state.thisStream = new GitWriteStream(); + state.thisStream.close = StreamCloseCallback; + state.thisStream.write = StreamWriteCallback; + state.thisStream.free = StreamFreeCallback; + + state.thisPtr = Marshal.AllocHGlobal(Marshal.SizeOf(state.thisStream)); + Marshal.StructureToPtr(state.thisStream, state.thisPtr, false); + + state.nextPtr = git_writestream_next; + state.nextStream = Marshal.PtrToStructure(state.nextPtr); + + state.filterSource = FilterSource.FromNativePtr(filterSourcePtr); + state.output = new WriteStream(state.nextStream, state.nextPtr); + + Create(state.filterSource.Path, state.filterSource.Root, state.filterSource.SourceMode); + + if (!activeStreams.TryAdd(state.thisPtr, state)) + { + // AFAICT this is a theoretical error that could only happen if we manage + // to free the stream pointer but fail to remove the dictionary entry. + throw new InvalidOperationException("Overlapping stream pointers"); + } + } + catch (Exception exception) + { + // unexpected failures means memory clean up required + if (state.thisPtr != IntPtr.Zero) + { + Marshal.FreeHGlobal(state.thisPtr); + state.thisPtr = IntPtr.Zero; + } + + Log.Write(LogLevel.Error, "Filter.StreamCreateCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + + git_writestream_out = state.thisPtr; + + return result; + } + + int StreamCloseCallback(IntPtr stream) + { + int result = 0; + StreamState state; + + try + { + Ensure.ArgumentNotZeroIntPtr(stream, "stream"); + + if (!activeStreams.TryGetValue(stream, out state)) + { + throw new ArgumentException("Unknown stream pointer", nameof(stream)); + } + + Ensure.ArgumentIsExpectedIntPtr(stream, state.thisPtr, "stream"); + + using (BufferedStream outputBuffer = new BufferedStream(state.output, BufferSize)) + { + Complete(state.filterSource.Path, state.filterSource.Root, outputBuffer); + } + + result = state.nextStream.close(state.nextPtr); + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.StreamCloseCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + + return result; + } + + void StreamFreeCallback(IntPtr stream) + { + StreamState state; + + try + { + Ensure.ArgumentNotZeroIntPtr(stream, "stream"); + + if (!activeStreams.TryRemove(stream, out state)) + { + throw new ArgumentException("Double free or invalid stream pointer", nameof(stream)); + } + + Ensure.ArgumentIsExpectedIntPtr(stream, state.thisPtr, "stream"); + + Marshal.FreeHGlobal(state.thisPtr); + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.StreamFreeCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + } + } + + unsafe int StreamWriteCallback(IntPtr stream, IntPtr buffer, UIntPtr len) + { + int result = 0; + StreamState state; + + try + { + Ensure.ArgumentNotZeroIntPtr(stream, "stream"); + Ensure.ArgumentNotZeroIntPtr(buffer, "buffer"); + + if (!activeStreams.TryGetValue(stream, out state)) + { + throw new ArgumentException("Invalid or already freed stream pointer", nameof(stream)); + } + + Ensure.ArgumentIsExpectedIntPtr(stream, state.thisPtr, "stream"); + + using (UnmanagedMemoryStream input = new UnmanagedMemoryStream((byte*)buffer.ToPointer(), (long)len)) + using (BufferedStream outputBuffer = new BufferedStream(state.output, BufferSize)) + { + switch (state.filterSource.SourceMode) + { + case FilterMode.Clean: + Clean(state.filterSource.Path, state.filterSource.Root, input, outputBuffer); + break; + + case FilterMode.Smudge: + Smudge(state.filterSource.Path, state.filterSource.Root, input, outputBuffer); + break; + + default: + Proxy.git_error_set_str(GitErrorCategory.Filter, "Unexpected filter mode."); + return (int)GitErrorCode.Ambiguous; + } + } + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, "Filter.StreamWriteCallback exception"); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Filter, exception); + result = (int)GitErrorCode.Error; + } + + return result; + } + } +} diff --git a/LibGit2Sharp/FilterAttributeEntry.cs b/LibGit2Sharp/FilterAttributeEntry.cs new file mode 100644 index 000000000..117523d3e --- /dev/null +++ b/LibGit2Sharp/FilterAttributeEntry.cs @@ -0,0 +1,53 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The definition for a given filter found in the .gitattributes file. + /// The filter definition will result as 'filter=filterName' + /// + /// In the .gitattributes file a filter will be matched to a pathspec like so + /// '*.txt filter=filterName' + /// + public class FilterAttributeEntry + { + private const string AttributeFilterDefinition = "filter="; + + private readonly string filterDefinition; + + /// + /// For testing purposes + /// + protected FilterAttributeEntry() { } + + /// + /// The name of the filter found in a .gitattributes file. + /// + /// The name of the filter as found in the .gitattributes file without the "filter=" prefix + /// + /// "filter=" will be prepended to the filterDefinition, therefore the "filter=" portion of the filter + /// name shouldbe omitted on declaration. Inclusion of the "filter=" prefix will cause the FilterDefinition to + /// fail to match the .gitattributes entry and thefore no be invoked correctly. + /// + public FilterAttributeEntry(string filterName) + { + Ensure.ArgumentNotNullOrEmptyString(filterName, "filterName"); + if (filterName.StartsWith("filter=", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("The filterName parameter should not begin with \"filter=\"", filterName); + } + + filterName = AttributeFilterDefinition + filterName; + this.filterDefinition = filterName; + } + + /// + /// The filter name in the form of 'filter=filterName' + /// + public virtual string FilterDefinition + { + get { return filterDefinition; } + } + } +} diff --git a/LibGit2Sharp/FilterMode.cs b/LibGit2Sharp/FilterMode.cs new file mode 100644 index 000000000..31a9546c1 --- /dev/null +++ b/LibGit2Sharp/FilterMode.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// These values control which direction of change is with which which a filter is being applied. + /// + /// + /// These enum values must be identical to the values in Libgit2 filter_mode_t found in filter.h + /// + public enum FilterMode + { + /// + /// Smudge occurs when exporting a file from the Git object database to the working directory. + /// For example, a file would be smudged during a checkout operation. + /// + Smudge = 0, + + /// + /// Clean occurs when importing a file from the working directory to the Git object database. + /// For example, a file would be cleaned when staging a file. + /// + Clean = 1, + } +} diff --git a/LibGit2Sharp/FilterRegistration.cs b/LibGit2Sharp/FilterRegistration.cs new file mode 100644 index 000000000..f6c1e31d0 --- /dev/null +++ b/LibGit2Sharp/FilterRegistration.cs @@ -0,0 +1,86 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// An object representing the registration of a Filter type with libgit2 + /// + public sealed class FilterRegistration + { + /// + /// Maximum priority value a filter can have. A value of 200 will be run last on checkout and first on checkin. + /// + public const int FilterPriorityMax = 200; + /// + /// Minimum priority value a filter can have. A value of 0 will be run first on checkout and last on checkin. + /// + public const int FilterPriorityMin = 0; + + /// + /// + /// + /// + /// + internal FilterRegistration(Filter filter, int priority) + { + System.Diagnostics.Debug.Assert(filter != null); + System.Diagnostics.Debug.Assert(priority >= FilterPriorityMin && priority <= FilterPriorityMax); + + Filter = filter; + Priority = priority; + + // marshal the git_filter strucutre into native memory + FilterPointer = Marshal.AllocHGlobal(Marshal.SizeOf(filter.GitFilter)); + Marshal.StructureToPtr(filter.GitFilter, FilterPointer, false); + + // register the filter with the native libary + Proxy.git_filter_register(filter.Name, FilterPointer, priority); + } + /// + /// Finalizer called by the , deregisters and frees native memory associated with the registered filter in libgit2. + /// + ~FilterRegistration() + { + // deregister the filter + GlobalSettings.DeregisterFilter(this); + // clean up native allocations + Free(); + } + + /// + /// Gets if the registration and underlying filter are valid. + /// + public bool IsValid { get { return !freed; } } + /// + /// The registerd filters + /// + public readonly Filter Filter; + /// + /// The name of the filter in the libgit2 registry + /// + public string Name { get { return Filter.Name; } } + /// + /// The priority of the registered filter + /// + public readonly int Priority; + + private readonly IntPtr FilterPointer; + + private bool freed; + + internal void Free() + { + if (!freed) + { + // unregister the filter with the native libary + Proxy.git_filter_unregister(Filter.Name); + // release native memory + Marshal.FreeHGlobal(FilterPointer); + // remember to not do this twice + freed = true; + } + } + } +} diff --git a/LibGit2Sharp/FilterSource.cs b/LibGit2Sharp/FilterSource.cs new file mode 100644 index 000000000..ab1dcb35c --- /dev/null +++ b/LibGit2Sharp/FilterSource.cs @@ -0,0 +1,66 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A filter source - describes the direction of filtering and the file being filtered. + /// + public class FilterSource + { + /// + /// Needed for mocking purposes + /// + protected FilterSource() { } + + internal unsafe FilterSource(FilePath path, FilterMode mode, git_filter_source* source) + { + SourceMode = mode; + ObjectId = ObjectId.BuildFromPtr(&source->oid); + Path = path.Native; + Root = Proxy.git_repository_workdir(new IntPtr(source->repository)).Native; + } + + /// + /// Take an unmanaged pointer and convert it to filter source callback paramater + /// + /// + /// + internal static unsafe FilterSource FromNativePtr(IntPtr ptr) + { + return FromNativePtr((git_filter_source*)ptr.ToPointer()); + } + + /// + /// Take an unmanaged pointer and convert it to filter source callback paramater + /// + /// + /// + internal static unsafe FilterSource FromNativePtr(git_filter_source* ptr) + { + FilePath path = LaxFilePathMarshaler.FromNative(ptr->path) ?? FilePath.Empty; + FilterMode gitFilterSourceMode = Proxy.git_filter_source_mode(ptr); + return new FilterSource(path, gitFilterSourceMode, ptr); + } + + /// + /// The filter mode for current file being filtered + /// + public virtual FilterMode SourceMode { get; private set; } + + /// + /// The relative path to the file + /// + public virtual string Path { get; private set; } + + /// + /// The blob id + /// + public virtual ObjectId ObjectId { get; private set; } + + /// + /// The working directory + /// + public virtual string Root { get; private set; } + } +} diff --git a/LibGit2Sharp/FilteringOptions.cs b/LibGit2Sharp/FilteringOptions.cs index f06d5a2a4..22988e62e 100644 --- a/LibGit2Sharp/FilteringOptions.cs +++ b/LibGit2Sharp/FilteringOptions.cs @@ -22,6 +22,6 @@ public FilteringOptions(string hintPath) /// The path to "hint" to the filters will be used to apply /// attributes. /// - public string HintPath { get; private set; } + public string HintPath { get; private set; } } } diff --git a/LibGit2Sharp/GitLink.cs b/LibGit2Sharp/GitLink.cs index 398ab3217..f03b1d719 100644 --- a/LibGit2Sharp/GitLink.cs +++ b/LibGit2Sharp/GitLink.cs @@ -16,8 +16,7 @@ protected GitLink() internal GitLink(Repository repo, ObjectId id) : base(repo, id) - { - } + { } private string DebuggerDisplay { diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 5e11489a2..f9813a3ea 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -1,9 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -15,20 +13,30 @@ public abstract class GitObject : IEquatable, IBelongToARepository { internal static IDictionary TypeToKindMap = new Dictionary - { - { typeof(Commit), ObjectType.Commit }, - { typeof(Tree), ObjectType.Tree }, - { typeof(Blob), ObjectType.Blob }, - { typeof(TagAnnotation), ObjectType.Tag }, - }; + { + { typeof(Commit), ObjectType.Commit }, + { typeof(Tree), ObjectType.Tree }, + { typeof(Blob), ObjectType.Blob }, + { typeof(TagAnnotation), ObjectType.Tag }, + }; + internal static IDictionary TypeToGitKindMap = + new Dictionary + { + { typeof(Commit), GitObjectType.Commit }, + { typeof(Tree), GitObjectType.Tree }, + { typeof(Blob), GitObjectType.Blob }, + { typeof(TagAnnotation), GitObjectType.Tag }, + }; private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Id); + private readonly ILazy lazyIsMissing; + /// /// The containing the object. /// - protected readonly Repository repo; + internal readonly Repository repo; /// /// Needed for mocking purposes. @@ -45,6 +53,7 @@ protected GitObject(Repository repo, ObjectId id) { this.repo = repo; Id = id; + lazyIsMissing = GitObjectLazyGroup.Singleton(repo, id, handle => handle == null, throwIfMissing: false); } /// @@ -52,49 +61,78 @@ protected GitObject(Repository repo, ObjectId id) /// public virtual ObjectId Id { get; private set; } + /// + /// Determine if the object is missing + /// + /// + /// This is common when dealing with partially cloned repositories as blobs or trees could be missing + /// + public virtual bool IsMissing => lazyIsMissing.Value; + /// /// Gets the 40 character sha1 of this object. /// - public virtual string Sha - { - get { return Id.Sha; } - } + public virtual string Sha => Id.Sha; - internal static GitObject BuildFrom(Repository repo, ObjectId id, GitObjectType type, FilePath path) + internal static GitObject BuildFrom(Repository repo, ObjectId id, GitObjectType type, string path) { switch (type) { case GitObjectType.Commit: return new Commit(repo, id); + case GitObjectType.Tree: return new Tree(repo, id, path); + case GitObjectType.Tag: return new TagAnnotation(repo, id); + case GitObjectType.Blob: return new Blob(repo, id); + default: - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unexpected type '{0}' for object '{1}'.", type, id)); + throw new LibGit2SharpException("Unexpected type '{0}' for object '{1}'.", + type, + id); } } - internal Commit DereferenceToCommit(bool throwsIfCanNotBeDereferencedToACommit) + internal T Peel(bool throwOnError) where T : GitObject { - using (GitObjectSafeHandle peeledHandle = Proxy.git_object_peel(repo.Handle, Id, GitObjectType.Commit, throwsIfCanNotBeDereferencedToACommit)) + GitObjectType kind; + if (!TypeToGitKindMap.TryGetValue(typeof(T), out kind)) + { + throw new ArgumentException("Invalid type passed to peel"); + } + + using (var handle = Proxy.git_object_peel(repo.Handle, Id, kind, throwOnError)) { - if (peeledHandle == null) + if (handle == null) { return null; } - return (Commit)BuildFrom(repo, Proxy.git_object_id(peeledHandle), GitObjectType.Commit, null); + return (T)BuildFrom(this.repo, Proxy.git_object_id(handle), kind, null); } } /// - /// Determines whether the specified is equal to the current . + /// Peel this object to the specified type + /// + /// It will throw if the object cannot be peeled to the type. + /// + /// The kind of to peel to. + /// The peeled object + public virtual T Peel() where T : GitObject + { + return Peel(true); + } + + /// + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as GitObject); @@ -142,7 +180,7 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// /// The that represents the current . public override string ToString() diff --git a/LibGit2Sharp/GitObjectMetadata.cs b/LibGit2Sharp/GitObjectMetadata.cs new file mode 100644 index 000000000..348fa4daa --- /dev/null +++ b/LibGit2Sharp/GitObjectMetadata.cs @@ -0,0 +1,34 @@ +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Exposes low level Git object metadata + /// + public sealed class GitObjectMetadata + { + private readonly GitObjectType type; + + /// + /// Size of the Object + /// + public long Size { get; private set; } + + /// + /// Object Type + /// + public ObjectType Type + { + get + { + return type.ToObjectType(); + } + } + + internal GitObjectMetadata(long size, GitObjectType type) + { + this.Size = size; + this.type = type; + } + } +} diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs index 08f7ad53d..9807155e7 100644 --- a/LibGit2Sharp/GlobalSettings.cs +++ b/LibGit2Sharp/GlobalSettings.cs @@ -1,4 +1,8 @@ using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -9,39 +13,74 @@ namespace LibGit2Sharp public static class GlobalSettings { private static readonly Lazy version = new Lazy(Version.Build); + private static readonly Dictionary registeredFilters; + private static readonly bool nativeLibraryPathAllowed; - /// - /// Returns all the optional features that were compiled into - /// libgit2. - /// - /// A enumeration. - [Obsolete("This method will be removed in the next release. Use Version.Features instead.")] - public static BuiltInFeatures Features() + private static LogConfiguration logConfiguration = LogConfiguration.None; + + private static string nativeLibraryPath; + private static bool nativeLibraryPathLocked; + private static readonly string nativeLibraryDefaultPath = null; + + static GlobalSettings() { - return Version.Features; + bool netFX = Platform.IsRunningOnNetFramework(); + bool netCore = Platform.IsRunningOnNetCore(); + + nativeLibraryPathAllowed = netFX || netCore; + +#if NETFRAMEWORK + if (netFX) + { + // For .NET Framework apps the dependencies are deployed to lib/win32/{architecture} directory + nativeLibraryDefaultPath = Path.Combine(GetExecutingAssemblyDirectory(), "lib", "win32", Platform.ProcessorArchitecture); + } +#endif + + registeredFilters = new Dictionary(); } - /// - /// Returns information related to the current LibGit2Sharp - /// library. - /// - public static Version Version +#if NETFRAMEWORK + private static string GetExecutingAssemblyDirectory() { - get + // Assembly.CodeBase is not actually a correctly formatted + // URI. It's merely prefixed with `file:///` and has its + // backslashes flipped. This is superior to EscapedCodeBase, + // which does not correctly escape things, and ambiguates a + // space (%20) with a literal `%20` in the path. Sigh. + var managedPath = Assembly.GetExecutingAssembly().CodeBase; + if (managedPath == null) + { + managedPath = Assembly.GetExecutingAssembly().Location; + } + else if (managedPath.StartsWith("file:///")) + { + managedPath = managedPath.Substring(8).Replace('/', '\\'); + } + else if (managedPath.StartsWith("file://")) { - return version.Value; + managedPath = @"\\" + managedPath.Substring(7).Replace('/', '\\'); } + + managedPath = Path.GetDirectoryName(managedPath); + return managedPath; } +#endif + + /// + /// Returns information related to the current LibGit2Sharp + /// library. + /// + public static Version Version => version.Value; /// /// Registers a new as a custom - /// smart-protocol transport with libgit2. Any Git remote with + /// smart-protocol transport with libgit2. Any Git remote with /// the scheme registered will delegate to the given transport - /// for all communication with the server. use this transport to communicate - /// with the server This is not commonly + /// for all communication with the server. This is not commonly /// used: some callers may want to re-use an existing connection to /// perform fetch / push operations to a remote. - /// + /// /// Note that this configuration is global to an entire process /// and does not honor application domains. /// @@ -56,10 +95,9 @@ public static SmartSubtransportRegistration RegisterSmartSubtransport(stri try { - Proxy.git_transport_register( - registration.Scheme, - registration.FunctionPointer, - registration.RegistrationPointer); + Proxy.git_transport_register(registration.Scheme, + registration.FunctionPointer, + registration.RegistrationPointer); } catch (Exception) { @@ -84,5 +122,321 @@ public static void UnregisterSmartSubtransport(SmartSubtransportRegistration< Proxy.git_transport_unregister(registration.Scheme); registration.Free(); } + + /// + /// Registers a new to receive + /// information logging information from libgit2 and LibGit2Sharp. + /// + /// Note that this configuration is global to an entire process + /// and does not honor application domains. + /// + public static LogConfiguration LogConfiguration + { + set + { + Ensure.ArgumentNotNull(value, "value"); + + logConfiguration = value; + + if (logConfiguration.Level == LogLevel.None) + { + Proxy.git_trace_set(0, null); + } + else + { + Proxy.git_trace_set(value.Level, value.GitTraceCallback); + + Log.Write(LogLevel.Info, "Logging enabled at level {0}", value.Level); + } + } + + get + { + return logConfiguration; + } + } + + /// + /// Sets a path for loading native binaries on .NET Framework or .NET Core. + /// When specified, native library will first be searched under the given path. + /// + /// If the library is not found it will be searched in standard search paths: + /// , + /// and + /// . + /// + /// This must be set before any other calls to the library, + /// and is not available on other platforms than .NET Framework and .NET Core. + /// + /// + public static string NativeLibraryPath + { + get + { + if (!nativeLibraryPathAllowed) + { + throw new LibGit2SharpException("Querying the native hint path is only supported on .NET Framework and .NET Core platforms"); + } + + return nativeLibraryPath ?? nativeLibraryDefaultPath; + } + + set + { + if (!nativeLibraryPathAllowed) + { + throw new LibGit2SharpException("Setting the native hint path is only supported on .NET Framework and .NET Core platforms"); + } + + if (nativeLibraryPathLocked) + { + throw new LibGit2SharpException("You cannot set the native library path after it has been loaded"); + } + + try + { + nativeLibraryPath = Path.GetFullPath(value); + } + catch (Exception e) + { + throw new LibGit2SharpException(e.Message); + } + } + } + + internal static string GetAndLockNativeLibraryPath() + { + nativeLibraryPathLocked = true; + return nativeLibraryPath ?? nativeLibraryDefaultPath; + } + + /// + /// Takes a snapshot of the currently registered filters. + /// + /// An array of . + public static IEnumerable GetRegisteredFilters() + { + lock (registeredFilters) + { + FilterRegistration[] array = new FilterRegistration[registeredFilters.Count]; + registeredFilters.Values.CopyTo(array, 0); + return array; + } + } + + /// + /// Register a filter globally with a default priority of 200 allowing the custom filter + /// to imitate a core Git filter driver. It will be run last on checkout and first on checkin. + /// + public static FilterRegistration RegisterFilter(Filter filter) + { + return RegisterFilter(filter, 200); + } + + /// + /// Registers a to be invoked when matches .gitattributes 'filter=name' + /// + /// The filter to be invoked at run time. + /// The priroty of the filter to invoked. + /// A value of 0 () will be run first on checkout and last on checkin. + /// A value of 200 () will be run last on checkout and first on checkin. + /// + /// A object used to manage the lifetime of the registration. + public static FilterRegistration RegisterFilter(Filter filter, int priority) + { + Ensure.ArgumentNotNull(filter, "filter"); + if (priority < FilterRegistration.FilterPriorityMin || priority > FilterRegistration.FilterPriorityMax) + { + throw new ArgumentOutOfRangeException(nameof(priority), + priority, + string.Format(System.Globalization.CultureInfo.InvariantCulture, + "Filter priorities must be within the inclusive range of [{0}, {1}].", + FilterRegistration.FilterPriorityMin, + FilterRegistration.FilterPriorityMax)); + } + + FilterRegistration registration = null; + + lock (registeredFilters) + { + // if the filter has already been registered + if (registeredFilters.ContainsKey(filter)) + { + throw new EntryExistsException("The filter has already been registered.", GitErrorCode.Exists, GitErrorCategory.Filter); + } + + // allocate the registration object + registration = new FilterRegistration(filter, priority); + // add the filter and registration object to the global tracking list + registeredFilters.Add(filter, registration); + } + + return registration; + } + + /// + /// Unregisters the associated filter. + /// + /// Registration object with an associated filter. + public static void DeregisterFilter(FilterRegistration registration) + { + Ensure.ArgumentNotNull(registration, "registration"); + + lock (registeredFilters) + { + var filter = registration.Filter; + + // do nothing if the filter isn't registered + if (registeredFilters.ContainsKey(filter)) + { + // remove the register from the global tracking list + registeredFilters.Remove(filter); + // clean up native allocations + registration.Free(); + } + } + } + + internal static void DeregisterFilter(Filter filter) + { + System.Diagnostics.Debug.Assert(filter != null); + + // do nothing if the filter isn't registered + if (registeredFilters.ContainsKey(filter)) + { + var registration = registeredFilters[filter]; + // unregister the filter + DeregisterFilter(registration); + } + } + + /// + /// Get the paths under which libgit2 searches for the configuration file of a given level. + /// + /// The level (global/system/XDG) of the config. + /// The paths that are searched + public static IEnumerable GetConfigSearchPaths(ConfigurationLevel level) + { + return Proxy.git_libgit2_opts_get_search_path(level).Split(Path.PathSeparator); + } + + /// + /// Set the paths under which libgit2 searches for the configuration file of a given level. + /// + /// . + /// + /// The level (global/system/XDG) of the config. + /// + /// The new search paths to set. + /// Pass null to reset to the default. + /// The special string "$PATH" will be substituted with the current search path. + /// + public static void SetConfigSearchPaths(ConfigurationLevel level, params string[] paths) + { + var pathString = (paths == null) ? null : string.Join(Path.PathSeparator.ToString(), paths); + Proxy.git_libgit2_opts_set_search_path(level, pathString); + } + + /// + /// Enable or disable strict hash verification. + /// + /// true to enable strict hash verification; false otherwise. + public static void SetStrictHashVerification(bool enabled) + { + Proxy.git_libgit2_opts_enable_strict_hash_verification(enabled); + } + + /// + /// Enable or disable the libgit2 cache + /// + /// true to enable the cache, false otherwise + public static void SetEnableCaching(bool enabled) + { + Proxy.git_libgit2_opts_set_enable_caching(enabled); + } + + /// + /// Enable or disable the ofs_delta capability + /// + /// true to enable the ofs_delta capability, false otherwise + public static void SetEnableOfsDelta(bool enabled) + { + Proxy.git_libgit2_opts_set_enable_ofsdelta(enabled); + } + + /// + /// Enable or disable the libgit2 strict_object_creation capability + /// + /// true to enable the strict_object_creation capability, false otherwise + public static void SetEnableStrictObjectCreation(bool enabled) + { + Proxy.git_libgit2_opts_set_enable_strictobjectcreation(enabled); + } + + /// + /// Sets the user-agent string to be used by the HTTP(S) transport. + /// Note that "git/2.0" will be prepended for compatibility. + /// + /// The user-agent string to use + public static void SetUserAgent(string userAgent) + { + Proxy.git_libgit2_opts_set_user_agent(userAgent); + } + + /// + /// Set that the given git extensions are supported by the caller. + /// + /// + /// Extensions supported by libgit2 may be negated by prefixing them with a `!`. For example: setting extensions to { "!noop", "newext" } indicates that the caller does not want + /// to support repositories with the `noop` extension but does want to support repositories with the `newext` extension. + /// + /// Supported extensions + public static void SetExtensions(params string[] extensions) + { + Proxy.git_libgit2_opts_set_extensions(extensions); + } + + /// + /// Returns the list of git extensions that are supported. + /// + /// + /// This is the list of built-in extensions supported by libgit2 and custom extensions that have been added with `SetExtensions`. Extensions that have been negated will not be returned. + /// + public static string[] GetExtensions() + { + return Proxy.git_libgit2_opts_get_extensions(); + } + + /// + /// Gets the user-agent string used by libgit2. + /// + /// The user-agent string. + /// + /// + public static string GetUserAgent() + { + return Proxy.git_libgit2_opts_get_user_agent(); + } + + /// + /// Gets the owner validation setting for repository directories. + /// + /// + public static bool GetOwnerValidation() + { + return Proxy.git_libgit2_opts_get_owner_validation(); + } + + /// + /// Sets whether repository directories should be owned by the current user. The default is to validate ownership. + /// + /// + /// Disabling owner validation can lead to security vulnerabilities (see CVE-2022-24765). + /// + /// true to enable owner validation; otherwise, false. + public static void SetOwnerValidation(bool enabled) + { + Proxy.git_libgit2_opts_set_owner_validation(enabled); + } } } diff --git a/LibGit2Sharp/Handlers.cs b/LibGit2Sharp/Handlers.cs index ef688c550..7e0b572c4 100644 --- a/LibGit2Sharp/Handlers.cs +++ b/LibGit2Sharp/Handlers.cs @@ -1,4 +1,7 @@ -namespace LibGit2Sharp.Handlers +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace LibGit2Sharp.Handlers { /// /// Delegate definition to handle Progress callback. @@ -30,6 +33,15 @@ /// Credential types which the server accepts public delegate Credentials CredentialsHandler(string url, string usernameFromUrl, SupportedCredentialTypes types); + /// + /// Delegate definition for the certificate validation + /// + /// The certificate which the server sent + /// The hostname which we tried to connect to + /// Whether libgit2 thinks this certificate is valid + /// True to continue, false to cancel + public delegate bool CertificateCheckHandler(Certificate certificate, bool valid, string host); + /// /// Delegate definition for transfer progress callback. /// @@ -37,6 +49,21 @@ /// True to continue, false to cancel. public delegate bool TransferProgressHandler(TransferProgress progress); + /// + /// Delegate definition to indicate that a repository is about to be operated on. + /// (In the context of a recursive operation). + /// + /// Context on the repository that is being operated on. + /// true to continue, false to cancel. + public delegate bool RepositoryOperationStarting(RepositoryOperationContext context); + + /// + /// Delegate definition to indicate that an operation is done in a repository. + /// (In the context of a recursive operation). + /// + /// Context on the repository that is being operated on. + public delegate void RepositoryOperationCompleted(RepositoryOperationContext context); + /// /// Delegate definition for callback reporting push network progress. /// @@ -55,6 +82,13 @@ /// True to continue, false to cancel. public delegate bool PackBuilderProgressHandler(PackBuilderStage stage, int current, int total); + /// + /// Provides information about what updates will be performed before a push occurs + /// + /// List of updates about to be performed via push + /// True to continue, false to cancel. + public delegate bool PrePushHandler(IEnumerable updates); + /// /// Delegate definition to handle reporting errors when updating references on the remote. /// @@ -96,6 +130,25 @@ /// The refspec which didn't match the default. public delegate void RemoteRenameFailureHandler(string problematicRefspec); + /// + /// Delegate definition for stash application callback. + /// + /// The current step of the stash application. + /// True to continue checkout operation; false to cancel checkout operation. + public delegate bool StashApplyProgressHandler(StashApplyProgress progress); + + /// + /// Delegate to report information on a rebase step that is about to be performed. + /// + /// + public delegate void RebaseStepStartingHandler(BeforeRebaseStepInfo beforeRebaseStep); + + /// + /// Delegate to report information on the rebase step that was just completed. + /// + /// + public delegate void RebaseStepCompletedHandler(AfterRebaseStepInfo afterRebaseStepInfo); + /// /// The stages of pack building. /// @@ -111,4 +164,12 @@ public enum PackBuilderStage /// Deltafying } + + /// + /// Delegate definition for logging. This callback will be used to + /// write logging messages in libgit2 and LibGit2Sharp. + /// + /// The level of the log message. + /// The log message. + public delegate void LogHandler(LogLevel level, string message); } diff --git a/LibGit2Sharp/HistoryDivergence.cs b/LibGit2Sharp/HistoryDivergence.cs index 27d84dd19..262c09c15 100644 --- a/LibGit2Sharp/HistoryDivergence.cs +++ b/LibGit2Sharp/HistoryDivergence.cs @@ -19,7 +19,7 @@ protected HistoryDivergence() internal HistoryDivergence(Repository repo, Commit one, Commit another) { - commonAncestor = new Lazy(() => repo.Commits.FindMergeBase(one, another)); + commonAncestor = new Lazy(() => repo.ObjectDatabase.FindMergeBase(one, another)); Tuple div = Proxy.git_graph_ahead_behind(repo.Handle, one, another); One = one; @@ -64,10 +64,7 @@ internal HistoryDivergence(Repository repo, Commit one, Commit another) /// public virtual Commit CommonAncestor { - get - { - return commonAncestor.Value; - } + get { return commonAncestor.Value; } } } diff --git a/LibGit2Sharp/IDiffResult.cs b/LibGit2Sharp/IDiffResult.cs new file mode 100644 index 000000000..5090de88e --- /dev/null +++ b/LibGit2Sharp/IDiffResult.cs @@ -0,0 +1,10 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Marker interface to identify Diff results. + /// + public interface IDiffResult : IDisposable + { } +} diff --git a/LibGit2Sharp/IQueryableCommitLog.cs b/LibGit2Sharp/IQueryableCommitLog.cs index 7642d4122..ab8a92136 100644 --- a/LibGit2Sharp/IQueryableCommitLog.cs +++ b/LibGit2Sharp/IQueryableCommitLog.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace LibGit2Sharp { @@ -15,19 +16,19 @@ public interface IQueryableCommitLog : ICommitLog ICommitLog QueryBy(CommitFilter filter); /// - /// Find the best possible merge base given two s. + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The first . - /// The second . - /// The merge base or null if none found. - Commit FindMergeBase(Commit first, Commit second); + /// The file's path. + /// A list of file history entries, ready to be enumerated. + IEnumerable QueryBy(string path); /// - /// Find the best possible merge base given two or more according to the . + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The s for which to find the merge base. - /// The strategy to leverage in order to find the merge base. - /// The merge base or null if none found. - Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy); + /// The file's path. + /// The options used to control which commits will be returned. + /// A list of file history entries, ready to be enumerated. + IEnumerable QueryBy(string path, CommitFilter filter); + } } diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index cc9de7fef..fd19f9659 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { @@ -53,7 +52,7 @@ public interface IRepository : IDisposable /// /// Provides access to diffing functionalities to show changes between the working tree and the index or a tree, changes between the index and a tree, changes between two trees, or changes between two files on disk. /// - Diff Diff {get;} + Diff Diff { get; } /// /// Gets the database. @@ -71,42 +70,17 @@ public interface IRepository : IDisposable SubmoduleCollection Submodules { get; } /// - /// Checkout the commit pointed at by the tip of the specified . - /// - /// If this commit is the current tip of the branch as it exists in the repository, the HEAD - /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The to check out. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - Branch Checkout(Branch branch, CheckoutOptions options, Signature signature); - - /// - /// Checkout the specified branch, reference or SHA. - /// - /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will - /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// + /// Worktrees in the repository. /// - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - Branch Checkout(string committishOrBranchSpec, CheckoutOptions options, Signature signature); + WorktreeCollection Worktrees { get; } /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// + /// Checkout the specified tree. /// - /// The to check out. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - Branch Checkout(Commit commit, CheckoutOptions options, Signature signature); + /// The to checkout. + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + void Checkout(Tree tree, IEnumerable paths, CheckoutOptions opts); /// /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. @@ -167,20 +141,16 @@ public interface IRepository : IDisposable /// /// Flavor of reset operation to perform. /// The target commit object. - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - void Reset(ResetMode resetMode, Commit commit, Signature signature, string logMessage); + void Reset(ResetMode resetMode, Commit commit); /// - /// Replaces entries in the with entries from the specified commit. + /// Sets to the specified commit and optionally resets the and + /// the content of the working tree to match. /// + /// Flavor of reset operation to perform. /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - void Reset(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions); + /// Collection of parameters controlling checkout behavior. + void Reset(ResetMode resetMode, Commit commit, CheckoutOptions options); /// /// Clean the working tree by removing files that are not under version control. @@ -223,6 +193,23 @@ public interface IRepository : IDisposable /// The of the merge. MergeResult Merge(string committish, Signature merger, MergeOptions options); + /// + /// Access to Rebase functionality. + /// + Rebase Rebase { get; } + + /// + /// Merge the reference that was recently fetched. This will merge + /// the branch on the fetched remote that corresponded to the + /// current local branch when we did the fetch. This is the + /// second step in performing a pull operation (after having + /// performed said fetch). + /// + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + MergeResult MergeFetchedRefs(Signature merger, MergeOptions options); + /// /// Cherry picks changes from the commit into the branch pointed at by HEAD. /// @@ -254,5 +241,46 @@ public interface IRepository : IDisposable /// Specifies optional parameters; if null, the defaults are used. /// The blame for the file. BlameHunkCollection Blame(string path, BlameOptions options); + + /// + /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commit. + /// + /// The relative path within the working directory to the file. + /// A representing the state of the parameter. + FileStatus RetrieveStatus(string filePath); + + /// + /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commit. + /// + /// If set, the options that control the status investigation. + /// A holding the state of all the files. + RepositoryStatus RetrieveStatus(StatusOptions options); + + /// + /// Finds the most recent annotated tag that is reachable from a commit. + /// + /// If the tag points to the commit, then only the tag is shown. Otherwise, + /// it suffixes the tag name with the number of additional commits on top + /// of the tagged object and the abbreviated object name of the most recent commit. + /// + /// + /// Optionally, the parameter allow to tweak the + /// search strategy (considering lightweight tags, or even branches as reference points) + /// and the formatting of the returned identifier. + /// + /// + /// The commit to be described. + /// Determines how the commit will be described. + /// A descriptive identifier for the commit based on the nearest annotated tag. + string Describe(Commit commit, DescribeOptions options); + + /// + /// Parse an extended SHA-1 expression and retrieve the object and the reference + /// mentioned in the revision (if any). + /// + /// An extended SHA-1 expression for the object to look up + /// The reference mentioned in the revision (if any) + /// The object which the revision resolves to + void RevParse(string revision, out Reference reference, out GitObject obj); } } diff --git a/LibGit2Sharp/Identity.cs b/LibGit2Sharp/Identity.cs new file mode 100644 index 000000000..faa4ec884 --- /dev/null +++ b/LibGit2Sharp/Identity.cs @@ -0,0 +1,70 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Represents the identity used when writing reflog entries. + /// + public sealed class Identity + { + private readonly string _name; + private readonly string _email; + + /// + /// Initializes a new instance of the class. + /// + /// The name. + /// The email. + public Identity(string name, string email) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(email, "email"); + Ensure.ArgumentDoesNotContainZeroByte(name, "name"); + Ensure.ArgumentDoesNotContainZeroByte(email, "email"); + + _name = name; + _email = email; + } + + /// + /// Gets the email. + /// + public string Email + { + get { return _email; } + } + + /// + /// Gets the name. + /// + public string Name + { + get { return _name; } + } + + internal SignatureHandle BuildNowSignatureHandle() + { + return Proxy.git_signature_now(Name, Email); + } + } + + internal static class IdentityHelpers + { + /// + /// Build the handle for the Indentity with the current time, or return a handle + /// to an empty signature. + /// + /// + /// + public static unsafe SignatureHandle SafeBuildNowSignatureHandle(this Identity identity) + { + if (identity == null) + { + return new SignatureHandle(null, false); + } + + return identity.BuildNowSignatureHandle(); + } + } +} diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 89bd9b156..321673606 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -3,8 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; -using System.Linq; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -17,7 +16,7 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Index : IEnumerable { - private readonly IndexSafeHandle handle; + private readonly IndexHandle handle; private readonly Repository repo; private readonly ConflictCollection conflicts; @@ -27,13 +26,16 @@ public class Index : IEnumerable protected Index() { } - internal Index(Repository repo) + internal Index(IndexHandle handle, Repository repo) { this.repo = repo; + this.handle = handle; + conflicts = new ConflictCollection(this); + } - handle = Proxy.git_repository_index(repo.Handle); - conflicts = new ConflictCollection(repo); - + internal Index(Repository repo) + : this(Proxy.git_repository_index(repo.Handle), repo) + { repo.RegisterForCleanup(handle); } @@ -43,18 +45,18 @@ internal Index(Repository repo, string indexPath) handle = Proxy.git_index_open(indexPath); Proxy.git_repository_set_index(repo.Handle, handle); - conflicts = new ConflictCollection(repo); + conflicts = new ConflictCollection(this); repo.RegisterForCleanup(handle); } - internal IndexSafeHandle Handle + internal IndexHandle Handle { get { return handle; } } /// - /// Gets the number of in the index. + /// Gets the number of in the . /// public virtual int Count { @@ -62,7 +64,7 @@ public virtual int Count } /// - /// Determines if the index is free from conflicts. + /// Determines if the is free from conflicts. /// public virtual bool IsFullyMerged { @@ -72,22 +74,22 @@ public virtual bool IsFullyMerged /// /// Gets the with the specified relative path. /// - public virtual IndexEntry this[string path] + public virtual unsafe IndexEntry this[string path] { get { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - IndexEntrySafeHandle entryHandle = Proxy.git_index_get_bypath(handle, path, 0); - return IndexEntry.BuildFromPtr(entryHandle); + git_index_entry* entry = Proxy.git_index_get_bypath(handle, path, 0); + return IndexEntry.BuildFromPtr(entry); } } - private IndexEntry this[int index] + private unsafe IndexEntry this[int index] { get { - IndexEntrySafeHandle entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index); + git_index_entry* entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index); return IndexEntry.BuildFromPtr(entryHandle); } } @@ -128,511 +130,194 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). - /// - /// If this path is ignored by configuration then it will not be staged unless is unset. - /// - /// The path of the file within the working directory. - /// If set, determines how paths will be staged. - public virtual void Stage(string path, StageOptions stageOptions = null) - { - Ensure.ArgumentNotNull(path, "path"); - - Stage(new[] { path }, stageOptions); - } - - /// - /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). - /// - /// Any paths (even those listed explicitly) that are ignored by configuration will not be staged unless is unset. + /// Replaces entries in the with entries from the specified . + /// + /// This overwrites all existing state in the . + /// /// - /// The collection of paths of the files within the working directory. - /// If set, determines how paths will be staged. - public virtual void Stage(IEnumerable paths, StageOptions stageOptions = null) + /// The to read the entries from. + public virtual void Replace(Tree source) { - Ensure.ArgumentNotNull(paths, "paths"); - - DiffModifiers diffModifiers = DiffModifiers.IncludeUntracked; - ExplicitPathsOptions explicitPathsOptions = stageOptions != null ? stageOptions.ExplicitPathsOptions : null; - - if (stageOptions != null && stageOptions.IncludeIgnored) - { - diffModifiers |= DiffModifiers.IncludeIgnored; - } - - var changes = repo.Diff.Compare(diffModifiers, paths, explicitPathsOptions); - - foreach (var treeEntryChanges in changes) + using (var obj = new ObjectSafeWrapper(source.Id, repo.Handle)) { - switch (treeEntryChanges.Status) - { - case ChangeKind.Unmodified: - continue; - - case ChangeKind.Deleted: - RemoveFromIndex(treeEntryChanges.Path); - continue; - - case ChangeKind.Added: - /* Fall through */ - case ChangeKind.Modified: - AddToIndex(treeEntryChanges.Path); - continue; - - default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Entry '{0}' bears an unexpected ChangeKind '{1}'", treeEntryChanges.Path, treeEntryChanges.Status)); - } + Proxy.git_index_read_fromtree(this, obj.ObjectPtr); } - - UpdatePhysicalIndex(); - } - - /// - /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). - /// - /// The path of the file within the working directory. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public virtual void Unstage(string path, ExplicitPathsOptions explicitPathsOptions = null) - { - Ensure.ArgumentNotNull(path, "path"); - - Unstage(new[] { path }, explicitPathsOptions); } /// - /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). + /// Clears all entries the . This is semantically equivalent to + /// creating an empty object and resetting the to that . + /// + /// This overwrites all existing state in the . + /// /// - /// The collection of paths of the files within the working directory. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public virtual void Unstage(IEnumerable paths, ExplicitPathsOptions explicitPathsOptions = null) + public virtual void Clear() { - Ensure.ArgumentNotNull(paths, "paths"); - - if (repo.Info.IsHeadUnborn) - { - var changes = repo.Diff.Compare(null, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None }); - - Reset(changes); - } - else - { - repo.Reset("HEAD", paths, explicitPathsOptions); - } + Proxy.git_index_clear(this); } - /// - /// Moves and/or renames a file in the working directory and promotes the change to the staging area. - /// - /// The path of the file within the working directory which has to be moved/renamed. - /// The target path of the file within the working directory. - public virtual void Move(string sourcePath, string destinationPath) + private void RemoveFromIndex(string relativePath) { - Move(new[] { sourcePath }, new[] { destinationPath }); + Proxy.git_index_remove_bypath(handle, relativePath); } /// - /// Moves and/or renames a collection of files in the working directory and promotes the changes to the staging area. + /// Removes a specified entry from the . /// - /// The paths of the files within the working directory which have to be moved/renamed. - /// The target paths of the files within the working directory. - public virtual void Move(IEnumerable sourcePaths, IEnumerable destinationPaths) + /// The path of the entry to be removed. + public virtual void Remove(string indexEntryPath) { - Ensure.ArgumentNotNull(sourcePaths, "sourcePaths"); - Ensure.ArgumentNotNull(destinationPaths, "destinationPaths"); - - //TODO: Move() should support following use cases: - // - Moving a file under a directory ('file' and 'dir' -> 'dir/file') - // - Moving a directory (and its content) under another directory ('dir1' and 'dir2' -> 'dir2/dir1/*') - - //TODO: Move() should throw when: - // - Moving a directory under a file - - IDictionary, Tuple> batch = PrepareBatch(sourcePaths, destinationPaths); - - if (batch.Count == 0) - { - throw new ArgumentNullException("sourcePaths"); - } - - foreach (KeyValuePair, Tuple> keyValuePair in batch) - { - string sourcePath = keyValuePair.Key.Item1; - string destPath = keyValuePair.Value.Item1; - - if (Directory.Exists(sourcePath) || Directory.Exists(destPath)) - { - throw new NotImplementedException(); - } - - FileStatus sourceStatus = keyValuePair.Key.Item2; - if (sourceStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.Removed, FileStatus.Untracked, FileStatus.Missing })) - { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unable to move file '{0}'. Its current status is '{1}'.", sourcePath, sourceStatus)); - } - - FileStatus desStatus = keyValuePair.Value.Item2; - if (desStatus.HasAny(new Enum[] { FileStatus.Nonexistent, FileStatus.Missing })) - { - continue; - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unable to overwrite file '{0}'. Its current status is '{1}'.", destPath, desStatus)); - } - - string wd = repo.Info.WorkingDirectory; - foreach (KeyValuePair, Tuple> keyValuePair in batch) - { - string from = keyValuePair.Key.Item1; - string to = keyValuePair.Value.Item1; - - RemoveFromIndex(from); - File.Move(Path.Combine(wd, from), Path.Combine(wd, to)); - AddToIndex(to); - } - - UpdatePhysicalIndex(); + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); + RemoveFromIndex(indexEntryPath); } /// - /// Removes a file from the staging area, and optionally removes it from the working directory as well. + /// Adds a file from the working directory in the . /// - /// If the file has already been deleted from the working directory, this method will only deal - /// with promoting the removal to the staging area. - /// - /// - /// The default behavior is to remove the file from the working directory as well. - /// - /// - /// When not passing a , the passed path will be treated as - /// a pathspec. You can for example use it to pass the relative path to a folder inside the working directory, - /// so that all files beneath this folders, and the folder itself, will be removed. + /// If an entry with the same path already exists in the , + /// the newly added one will overwrite it. /// /// - /// The path of the file within the working directory. - /// True to remove the file from the working directory, False otherwise. - /// - /// If set, the passed will be treated as an explicit path. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public virtual void Remove(string path, bool removeFromWorkingDirectory = true, ExplicitPathsOptions explicitPathsOptions = null) + /// The path, in the working directory, of the file to be added. + public virtual void Add(string pathInTheWorkdir) { - Ensure.ArgumentNotNull(path, "path"); - - Remove(new[] { path }, removeFromWorkingDirectory, explicitPathsOptions); + Ensure.ArgumentNotNull(pathInTheWorkdir, "pathInTheWorkdir"); + Proxy.git_index_add_bypath(handle, pathInTheWorkdir); } /// - /// Removes a collection of fileS from the staging, and optionally removes them from the working directory as well. + /// Adds an entry in the from a . /// - /// If a file has already been deleted from the working directory, this method will only deal - /// with promoting the removal to the staging area. - /// - /// - /// The default behavior is to remove the files from the working directory as well. - /// - /// - /// When not passing a , the passed paths will be treated as - /// a pathspec. You can for example use it to pass the relative paths to folders inside the working directory, - /// so that all files beneath these folders, and the folders themselves, will be removed. + /// If an entry with the same path already exists in the , + /// the newly added one will overwrite it. /// /// - /// The collection of paths of the files within the working directory. - /// True to remove the files from the working directory, False otherwise. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public virtual void Remove(IEnumerable paths, bool removeFromWorkingDirectory = true, ExplicitPathsOptions explicitPathsOptions = null) + /// The which content should be added to the . + /// The path to be used in the . + /// Either , + /// or . + public virtual void Add(Blob blob, string indexEntryPath, Mode indexEntryMode) { - Ensure.ArgumentNotNullOrEmptyEnumerable(paths, "paths"); - - var pathsToDelete = paths.Where(p => Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, p))).ToList(); - var notConflictedPaths = new List(); - - foreach (var path in paths) - { - Ensure.ArgumentNotNullOrEmptyString(path, "path"); - - var conflict = repo.Index.Conflicts[path]; - - if (conflict != null) - { - pathsToDelete.Add(RemoveFromIndex(path)); - } - else - { - notConflictedPaths.Add(path); - } - } - - if (notConflictedPaths.Count > 0) - { - pathsToDelete.AddRange(RemoveStagedItems(notConflictedPaths, removeFromWorkingDirectory, explicitPathsOptions)); - } - - if (removeFromWorkingDirectory) - { - RemoveFilesAndFolders(pathsToDelete); - } - - UpdatePhysicalIndex(); + Ensure.ArgumentConformsTo(indexEntryMode, m => m.HasAny(TreeEntryDefinition.BlobModes), "indexEntryMode"); + Ensure.ArgumentNotNull(blob, "blob"); + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); + AddEntryToTheIndex(indexEntryPath, blob.Id, indexEntryMode); } - private IEnumerable RemoveStagedItems(IEnumerable paths, bool removeFromWorkingDirectory = true, ExplicitPathsOptions explicitPathsOptions = null) + internal void Replace(TreeChanges changes) { - var removed = new List(); - var changes = repo.Diff.Compare(DiffModifiers.IncludeUnmodified | DiffModifiers.IncludeUntracked, paths, explicitPathsOptions); - - foreach (var treeEntryChanges in changes) + foreach (TreeEntryChanges treeEntryChanges in changes) { - var status = repo.Index.RetrieveStatus(treeEntryChanges.Path); - switch (treeEntryChanges.Status) { - case ChangeKind.Added: - case ChangeKind.Deleted: - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); - break; - case ChangeKind.Unmodified: - if (removeFromWorkingDirectory && ( - status.HasFlag(FileStatus.Staged) || - status.HasFlag(FileStatus.Added))) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has changes staged in the index. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", - treeEntryChanges.Path)); - } - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); continue; + case ChangeKind.Added: + RemoveFromIndex(treeEntryChanges.Path); + continue; + + case ChangeKind.Deleted: case ChangeKind.Modified: - if (status.HasFlag(FileStatus.Modified) && status.HasFlag(FileStatus.Staged)) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has staged content different from both the working directory and the HEAD.", - treeEntryChanges.Path)); - } - if (removeFromWorkingDirectory) - { - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}', as it has local modifications. You can call the Remove() method with removeFromWorkingDirectory=false if you want to remove it from the index only.", - treeEntryChanges.Path)); - } - removed.Add(RemoveFromIndex(treeEntryChanges.Path)); + AddEntryToTheIndex(treeEntryChanges.OldPath, + treeEntryChanges.OldOid, + treeEntryChanges.OldMode); continue; default: - throw new RemoveFromIndexException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}'. Its current status is '{1}'.", - treeEntryChanges.Path, treeEntryChanges.Status)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected ChangeKind '{1}'", + treeEntryChanges.Path, + treeEntryChanges.Status)); } } - - return removed; - } - - private void RemoveFilesAndFolders(IEnumerable pathsList) - { - string wd = repo.Info.WorkingDirectory; - - foreach (string path in pathsList) - { - string fileName = Path.Combine(wd, path); - - if (Directory.Exists(fileName)) - { - Directory.Delete(fileName, true); - continue; - } - - if (!File.Exists(fileName)) - { - continue; - } - - File.Delete(fileName); - } - } - - /// - /// Replaces entries in the staging area with entries from the specified tree. - /// - /// This overwrites all existing state in the staging area. - /// - /// - /// The to read the entries from. - public virtual void Reset(Tree source) - { - using (var obj = new ObjectSafeWrapper(source.Id, repo.Handle)) - { - Proxy.git_index_read_fromtree(this, obj.ObjectPtr); - } - - UpdatePhysicalIndex(); } /// - /// Clears all entries the index. This is semantically equivalent to - /// creating an empty tree object and resetting the index to that tree. - /// - /// This overwrites all existing state in the staging area. - /// + /// Gets the conflicts that exist. /// - public virtual void Clear() - { - Proxy.git_index_clear(this); - UpdatePhysicalIndex(); - } - - private IDictionary, Tuple> PrepareBatch(IEnumerable leftPaths, IEnumerable rightPaths) - { - IDictionary, Tuple> dic = new Dictionary, Tuple>(); - - IEnumerator leftEnum = leftPaths.GetEnumerator(); - IEnumerator rightEnum = rightPaths.GetEnumerator(); - - while (Enumerate(leftEnum, rightEnum)) - { - Tuple from = BuildFrom(leftEnum.Current); - Tuple to = BuildFrom(rightEnum.Current); - dic.Add(from, to); - } - - return dic; - } - - private Tuple BuildFrom(string path) + public virtual ConflictCollection Conflicts { - string relativePath = repo.BuildRelativePathFrom(path); - return new Tuple(relativePath, RetrieveStatus(relativePath)); + get { return conflicts; } } - private static bool Enumerate(IEnumerator leftEnum, IEnumerator rightEnum) + private unsafe void AddEntryToTheIndex(string path, ObjectId id, Mode mode) { - bool isLeftEoF = leftEnum.MoveNext(); - bool isRightEoF = rightEnum.MoveNext(); - - if (isLeftEoF == isRightEoF) + IntPtr pathPtr = StrictFilePathMarshaler.FromManaged(path); + var indexEntry = new git_index_entry { - return isLeftEoF; - } + mode = (uint)mode, + path = (char*)pathPtr, + }; + Marshal.Copy(id.RawId, 0, new IntPtr(indexEntry.id.Id), GitOid.Size); - throw new ArgumentException("The collection of paths are of different lengths."); + Proxy.git_index_add(handle, &indexEntry); + EncodingMarshaler.Cleanup(pathPtr); } - private void AddToIndex(string relativePath) + private string DebuggerDisplay { - if (!repo.Submodules.TryStage(relativePath, true)) + get { - Proxy.git_index_add_bypath(handle, relativePath); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", Count); } } - private string RemoveFromIndex(string relativePath) - { - Proxy.git_index_remove_bypath(handle, relativePath); - - return relativePath; - } - - private void UpdatePhysicalIndex() + /// + /// Replaces entries in the with entries from the specified . + /// + /// The target object. + public virtual void Replace(Commit commit) { - Proxy.git_index_write(handle); + Replace(commit, null, null); } /// - /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commmit. + /// Replaces entries in the with entries from the specified . /// - /// The relative path within the working directory to the file. - /// A representing the state of the parameter. - public virtual FileStatus RetrieveStatus(string filePath) + /// The target object. + /// The list of paths (either files or directories) that should be considered. + public virtual void Replace(Commit commit, IEnumerable paths) { - Ensure.ArgumentNotNullOrEmptyString(filePath, "filePath"); - - string relativePath = repo.BuildRelativePathFrom(filePath); - - return Proxy.git_status_file(repo.Handle, relativePath); + Replace(commit, paths, null); } /// - /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commmit. + /// Replaces entries in the with entries from the specified . /// - /// If set, the options that control the status investigation. - /// A holding the state of all the files. - public virtual RepositoryStatus RetrieveStatus(StatusOptions options = null) + /// The target object. + /// The list of paths (either files or directories) that should be considered. + /// + /// If set, the passed will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths should be handled. + /// + public virtual void Replace(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions) { - ReloadFromDisk(); - - return new RepositoryStatus(repo, options); - } + Ensure.ArgumentNotNull(commit, "commit"); - internal void Reset(TreeChanges changes) - { - foreach (TreeEntryChanges treeEntryChanges in changes) + using (var changes = repo.Diff.Compare(commit.Tree, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None })) { - switch (treeEntryChanges.Status) - { - case ChangeKind.Unmodified: - continue; - - case ChangeKind.Added: - RemoveFromIndex(treeEntryChanges.Path); - continue; - - case ChangeKind.Deleted: - /* Fall through */ - case ChangeKind.Modified: - ReplaceIndexEntryWith(treeEntryChanges); - continue; - - default: - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Entry '{0}' bears an unexpected ChangeKind '{1}'", treeEntryChanges.Path, treeEntryChanges.Status)); - } + Replace(changes); } - - UpdatePhysicalIndex(); } /// - /// Gets the conflicts that exist. + /// Write the contents of this to disk /// - public virtual ConflictCollection Conflicts + public virtual void Write() { - get - { - return conflicts; - } - } - - private void ReplaceIndexEntryWith(TreeEntryChanges treeEntryChanges) - { - var indexEntry = new GitIndexEntry - { - Mode = (uint)treeEntryChanges.OldMode, - Id = treeEntryChanges.OldOid.Oid, - Path = StrictFilePathMarshaler.FromManaged(treeEntryChanges.OldPath), - }; - - Proxy.git_index_add(handle, indexEntry); - EncodingMarshaler.Cleanup(indexEntry.Path); - } - - internal void ReloadFromDisk() - { - Proxy.git_index_read(handle); + Proxy.git_index_write(handle); } - private string DebuggerDisplay + /// + /// Write the contents of this to a tree + /// + /// + public virtual Tree WriteToTree() { - get - { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", Count); - } + var treeId = Proxy.git_index_write_tree_to(this.handle, this.repo.Handle); + var result = this.repo.Lookup(treeId); + return result; } } } diff --git a/LibGit2Sharp/IndexEntry.cs b/LibGit2Sharp/IndexEntry.cs index d6e97eb03..554d9a9f1 100644 --- a/LibGit2Sharp/IndexEntry.cs +++ b/LibGit2Sharp/IndexEntry.cs @@ -30,36 +30,40 @@ public class IndexEntry : IEquatable /// public virtual StageLevel StageLevel { get; private set; } + /// + /// Whether the file is marked as assume-unchanged + /// + public virtual bool AssumeUnchanged { get; private set; } + /// /// Gets the id of the pointed at by this index entry. /// public virtual ObjectId Id { get; private set; } - internal static IndexEntry BuildFromPtr(IndexEntrySafeHandle handle) + internal static unsafe IndexEntry BuildFromPtr(git_index_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexEntry entry = handle.MarshalAsGitIndexEntry(); - - FilePath path = LaxFilePathMarshaler.FromNative(entry.Path); + string path = LaxUtf8Marshaler.FromNative(entry->path); return new IndexEntry - { - Path = path.Native, - Id = entry.Id, - StageLevel = Proxy.git_index_entry_stage(handle), - Mode = (Mode)entry.Mode - }; + { + Path = path, + Id = new ObjectId(entry->id.Id), + StageLevel = Proxy.git_index_entry_stage(entry), + Mode = (Mode)entry->mode, + AssumeUnchanged = (git_index_entry.GIT_IDXENTRY_VALID & entry->flags) == git_index_entry.GIT_IDXENTRY_VALID + }; } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as IndexEntry); @@ -111,7 +115,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} ({1}) => \"{2}\"", Path, StageLevel, Id.ToString(7)); + "{0} ({1}) => \"{2}\"", + Path, + StageLevel, + Id.ToString(7)); } } } diff --git a/LibGit2Sharp/IndexNameEntry.cs b/LibGit2Sharp/IndexNameEntry.cs index ef6fc50d2..40c202acc 100644 --- a/LibGit2Sharp/IndexNameEntry.cs +++ b/LibGit2Sharp/IndexNameEntry.cs @@ -22,21 +22,22 @@ public class IndexNameEntry : IEquatable protected IndexNameEntry() { } - internal static IndexNameEntry BuildFromPtr(IndexNameEntrySafeHandle handle) + internal static unsafe IndexNameEntry BuildFromPtr(git_index_name_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexNameEntry entry = handle.MarshalAsGitIndexNameEntry(); - - string ancestor = entry.Ancestor != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Ancestor).Native : null; - string ours = entry.Ours != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Ours).Native : null; - string theirs = entry.Theirs != IntPtr.Zero ? - LaxFilePathMarshaler.FromNative(entry.Theirs).Native : null; + string ancestor = entry->ancestor != null + ? LaxFilePathMarshaler.FromNative(entry->ancestor).Native + : null; + string ours = entry->ours != null + ? LaxFilePathMarshaler.FromNative(entry->ours).Native + : null; + string theirs = entry->theirs != null + ? LaxFilePathMarshaler.FromNative(entry->theirs).Native + : null; return new IndexNameEntry { @@ -62,10 +63,10 @@ internal static IndexNameEntry BuildFromPtr(IndexNameEntrySafeHandle handle) public virtual string Theirs { get; private set; } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as IndexNameEntry); @@ -117,7 +118,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} {1} {2}", Ancestor, Ours, Theirs); + "{0} {1} {2}", + Ancestor, + Ours, + Theirs); } } } diff --git a/LibGit2Sharp/IndexNameEntryCollection.cs b/LibGit2Sharp/IndexNameEntryCollection.cs index 30dff911b..a75bedd71 100644 --- a/LibGit2Sharp/IndexNameEntryCollection.cs +++ b/LibGit2Sharp/IndexNameEntryCollection.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -14,7 +13,7 @@ namespace LibGit2Sharp /// public class IndexNameEntryCollection : IEnumerable { - private readonly Repository repo; + private readonly Index index; /// /// Needed for mocking purposes. @@ -22,16 +21,16 @@ public class IndexNameEntryCollection : IEnumerable protected IndexNameEntryCollection() { } - internal IndexNameEntryCollection(Repository repo) + internal IndexNameEntryCollection(Index index) { - this.repo = repo; + this.index = index; } - private IndexNameEntry this[int index] + private unsafe IndexNameEntry this[int idx] { get { - IndexNameEntrySafeHandle entryHandle = Proxy.git_index_name_get_byindex(repo.Index.Handle, (UIntPtr)index); + git_index_name_entry* entryHandle = Proxy.git_index_name_get_byindex(index.Handle, (UIntPtr)idx); return IndexNameEntry.BuildFromPtr(entryHandle); } } @@ -42,7 +41,7 @@ private List AllIndexNames() { var list = new List(); - int count = Proxy.git_index_name_entrycount(repo.Index.Handle); + int count = Proxy.git_index_name_entrycount(index.Handle); for (int i = 0; i < count; i++) { diff --git a/LibGit2Sharp/IndexReucEntry.cs b/LibGit2Sharp/IndexReucEntry.cs index ffba71e07..becd20122 100644 --- a/LibGit2Sharp/IndexReucEntry.cs +++ b/LibGit2Sharp/IndexReucEntry.cs @@ -15,9 +15,9 @@ public class IndexReucEntry : IEquatable { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Path, - x => x.AncestorId, x => x.AncestorMode, - x => x.OurId, x => x.OurMode, - x => x.TheirId, x => x.TheirMode); + x => x.AncestorId, x => x.AncestorMode, + x => x.OurId, x => x.OurMode, + x => x.TheirId, x => x.TheirMode); /// /// Needed for mocking purposes. @@ -25,26 +25,24 @@ public class IndexReucEntry : IEquatable protected IndexReucEntry() { } - internal static IndexReucEntry BuildFromPtr(IndexReucEntrySafeHandle handle) + internal static unsafe IndexReucEntry BuildFromPtr(git_index_reuc_entry* entry) { - if (handle == null || handle.IsZero) + if (entry == null) { return null; } - GitIndexReucEntry entry = handle.MarshalAsGitIndexReucEntry(); - - FilePath path = LaxFilePathMarshaler.FromNative(entry.Path); + FilePath path = LaxUtf8Marshaler.FromNative(entry->Path); return new IndexReucEntry { Path = path.Native, - AncestorId = entry.AncestorId, - AncestorMode = (Mode)entry.AncestorMode, - OurId = entry.OurId, - OurMode = (Mode)entry.OurMode, - TheirId = entry.TheirId, - TheirMode = (Mode)entry.TheirMode, + AncestorId = ObjectId.BuildFromPtr(&entry->AncestorId), + AncestorMode = (Mode)entry->AncestorMode, + OurId = ObjectId.BuildFromPtr(&entry->OurId), + OurMode = (Mode)entry->OurMode, + TheirId = ObjectId.BuildFromPtr(&entry->TheirId), + TheirMode = (Mode)entry->TheirMode, }; } @@ -90,10 +88,10 @@ internal static IndexReucEntry BuildFromPtr(IndexReucEntrySafeHandle handle) public virtual Mode TheirMode { get; private set; } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as IndexReucEntry); @@ -145,7 +143,11 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0}: {1} {2} {3}", Path, AncestorId, OurId, TheirId); + "{0}: {1} {2} {3}", + Path, + AncestorId, + OurId, + TheirId); } } } diff --git a/LibGit2Sharp/IndexReucEntryCollection.cs b/LibGit2Sharp/IndexReucEntryCollection.cs index 7a81e3b41..818bce70c 100644 --- a/LibGit2Sharp/IndexReucEntryCollection.cs +++ b/LibGit2Sharp/IndexReucEntryCollection.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -14,7 +13,7 @@ namespace LibGit2Sharp /// public class IndexReucEntryCollection : IEnumerable { - private readonly Repository repo; + private readonly Index index; /// /// Needed for mocking purposes. @@ -22,30 +21,30 @@ public class IndexReucEntryCollection : IEnumerable protected IndexReucEntryCollection() { } - internal IndexReucEntryCollection(Repository repo) + internal IndexReucEntryCollection(Index index) { - this.repo = repo; + this.index = index; } /// /// Gets the with the specified relative path. /// - public virtual IndexReucEntry this[string path] + public virtual unsafe IndexReucEntry this[string path] { get { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - IndexReucEntrySafeHandle entryHandle = Proxy.git_index_reuc_get_bypath(repo.Index.Handle, path); + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_bypath(index.Handle, path); return IndexReucEntry.BuildFromPtr(entryHandle); } } - private IndexReucEntry this[int index] + private unsafe IndexReucEntry this[int idx] { get { - IndexReucEntrySafeHandle entryHandle = Proxy.git_index_reuc_get_byindex(repo.Index.Handle, (UIntPtr)index); + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_byindex(index.Handle, (UIntPtr)idx); return IndexReucEntry.BuildFromPtr(entryHandle); } } @@ -56,7 +55,7 @@ private List AllIndexReucs() { var list = new List(); - int count = Proxy.git_index_reuc_entrycount(repo.Index.Handle); + int count = Proxy.git_index_reuc_entrycount(index.Handle); for (int i = 0; i < count; i++) { diff --git a/LibGit2Sharp/InvalidSpecificationException.cs b/LibGit2Sharp/InvalidSpecificationException.cs index 5113f8c63..d9625dc32 100644 --- a/LibGit2Sharp/InvalidSpecificationException.cs +++ b/LibGit2Sharp/InvalidSpecificationException.cs @@ -1,21 +1,27 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// The exception that is thrown when the provided specification is syntactically incorrect. + /// The exception that is thrown when a provided specification is bad. This + /// can happen if the provided specification is syntactically incorrect, or + /// if the spec refers to an object of an incorrect type (e.g. asking to + /// create a branch from a blob, or peeling a blob to a commit). /// +#if NETFRAMEWORK [Serializable] - public class InvalidSpecificationException : LibGit2SharpException +#endif + public class InvalidSpecificationException : NativeException { /// /// Initializes a new instance of the class. /// public InvalidSpecificationException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +29,16 @@ public InvalidSpecificationException() /// A message that describes the error. public InvalidSpecificationException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public InvalidSpecificationException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +47,9 @@ public InvalidSpecificationException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public InvalidSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +57,19 @@ public InvalidSpecificationException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected InvalidSpecificationException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal InvalidSpecificationException(string message, GitErrorCategory category) + : base(message, category) + { } - internal InvalidSpecificationException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.InvalidSpecification; + } } } } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index ba00f56df..1c4abef7b 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,366 +1,61 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {EE6ED99F-CB12-4683-B055-D28FC7357A34} - Library - Properties - LibGit2Sharp - LibGit2Sharp - v4.0 - 512 - + net472;net8.0 + 12.0 + true + LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .NET + LibGit2Sharp contributors + Copyright © LibGit2Sharp contributors + libgit2 git + https://github.com/libgit2/libgit2sharp/ + LibGit2Sharp contributors + true + true + embedded + true + ..\libgit2sharp.snk + square-logo.png + App_Readme/README.md + App_Readme/LICENSE.md + true + $(ArtifactsPath)\package + preview.0 + libgit2-$(libgit2_hash.Substring(0,7)) - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET40 - prompt - 4 - false - true - AllRules.ruleset - bin\Debug\LibGit2Sharp.xml + + + true - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - false - bin\Release\LibGit2Sharp.xml - - - true - full - false - bin\Leaks\ - TRACE;DEBUG;NET40;LEAKS - prompt - 4 - false - true - AllRules.ruleset - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - + - + + + - + + + + + + + - - - - - - - + + + + + + + https://github.com/libgit2/libgit2sharp/blob/$(SourceRevisionId)/CHANGES.md + - - - - - + + + + $(MinVerMajor).$(MinVerMinor).0.0 + - \ No newline at end of file + + diff --git a/LibGit2Sharp/LibGit2Sharp.v2.ncrunchproject b/LibGit2Sharp/LibGit2Sharp.v2.ncrunchproject deleted file mode 100644 index bb6727939..000000000 --- a/LibGit2Sharp/LibGit2Sharp.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - ..\Lib\NativeBinaries\**.* - \ No newline at end of file diff --git a/LibGit2Sharp/LibGit2SharpException.cs b/LibGit2Sharp/LibGit2SharpException.cs index f5af473fd..0518fa757 100644 --- a/LibGit2Sharp/LibGit2SharpException.cs +++ b/LibGit2Sharp/LibGit2SharpException.cs @@ -1,22 +1,24 @@ using System; using System.Globalization; +#if NETFRAMEWORK using System.Runtime.Serialization; -using LibGit2Sharp.Core; +#endif namespace LibGit2Sharp { /// /// The exception that is thrown when an error occurs during application execution. /// +#if NETFRAMEWORK [Serializable] +#endif public class LibGit2SharpException : Exception { /// /// Initializes a new instance of the class. /// public LibGit2SharpException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +26,7 @@ public LibGit2SharpException() /// A message that describes the error. public LibGit2SharpException(string message) : base(message) - { - } + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,9 +35,19 @@ public LibGit2SharpException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public LibGit2SharpException(string message, Exception innerException) : base(message, innerException) + { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public LibGit2SharpException(string format, params object[] args) + : base(string.Format(CultureInfo.InvariantCulture, format, args)) { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -44,14 +55,7 @@ public LibGit2SharpException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected LibGit2SharpException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - - internal LibGit2SharpException(string message, GitErrorCode code, GitErrorCategory category) : this(message) - { - Data.Add("libgit2.code", (int)code); - Data.Add("libgit2.category", (int)category); - - } + { } +#endif } } diff --git a/LibGit2Sharp/Line.cs b/LibGit2Sharp/Line.cs new file mode 100644 index 000000000..830247fc3 --- /dev/null +++ b/LibGit2Sharp/Line.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Represents a line with line number and content. + /// + public struct Line + { + /// + /// The line number of the original line in the blob. + /// + public int LineNumber { get; } + + /// + /// The content of the line in the original blob. + /// + public string Content { get; } + + internal Line(int lineNumber, string content) + { + LineNumber = lineNumber; + Content = content; + } + } +} diff --git a/LibGit2Sharp/LockedFileException.cs b/LibGit2Sharp/LockedFileException.cs index a086a7b0b..b38f40496 100644 --- a/LibGit2Sharp/LockedFileException.cs +++ b/LibGit2Sharp/LockedFileException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// /// The exception that is thrown attempting to open a locked file. /// +#if NETFRAMEWORK [Serializable] - public class LockedFileException : LibGit2SharpException +#endif + public class LockedFileException : NativeException { /// /// Initializes a new instance of the class. /// public LockedFileException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public LockedFileException() /// A message that describes the error. public LockedFileException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public LockedFileException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public LockedFileException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public LockedFileException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +54,19 @@ public LockedFileException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected LockedFileException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal LockedFileException(string message, GitErrorCategory category) + : base(message, category) + { } - internal LockedFileException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.LockedFile; + } } } } diff --git a/LibGit2Sharp/Log.cs b/LibGit2Sharp/Log.cs new file mode 100644 index 000000000..f8303fa01 --- /dev/null +++ b/LibGit2Sharp/Log.cs @@ -0,0 +1,27 @@ +namespace LibGit2Sharp +{ + internal class Log + { + private static bool IsEnabled(LogConfiguration configuration, LogLevel level) + { + return (configuration.Level != LogLevel.None && configuration.Level >= level); + } + + internal static bool IsEnabled(LogLevel level) + { + return IsEnabled(GlobalSettings.LogConfiguration, level); + } + + internal static void Write(LogLevel level, string message, params object[] args) + { + LogConfiguration configuration = GlobalSettings.LogConfiguration; + + if (!IsEnabled(configuration, level)) + { + return; + } + + configuration.Handler(level, string.Format(message, args)); + } + } +} diff --git a/LibGit2Sharp/LogConfiguration.cs b/LibGit2Sharp/LogConfiguration.cs new file mode 100644 index 000000000..dd63bf308 --- /dev/null +++ b/LibGit2Sharp/LogConfiguration.cs @@ -0,0 +1,53 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Logging and tracing configuration for libgit2 and LibGit2Sharp. + /// + public sealed class LogConfiguration + { + /// + /// The default logging configuration, which performs no logging at all. + /// + public static readonly LogConfiguration None = new LogConfiguration { Level = LogLevel.None }; + + /// + /// Creates a new logging configuration to call the given + /// delegate when logging occurs at the given level. + /// + /// Level to log at + /// Handler to call when logging occurs + public LogConfiguration(LogLevel level, LogHandler handler) + { + Ensure.ArgumentConformsTo(level, (t) => { return (level != LogLevel.None); }, "level"); + Ensure.ArgumentNotNull(handler, "handler"); + + Level = level; + Handler = handler; + + // Explicitly create (and hold a reference to) a callback-delegate to wrap GitTraceHandler(). + GitTraceCallback = GitTraceHandler; + } + + private LogConfiguration() + { } + + internal LogLevel Level { get; private set; } + internal LogHandler Handler { get; private set; } + internal NativeMethods.git_trace_cb GitTraceCallback { get; private set; } + + /// + /// This private method will be called from LibGit2 (from C code via + /// the GitTraceCallback delegate) to route LibGit2 log messages to + /// the same LogHandler as LibGit2Sharp messages. + /// + private void GitTraceHandler(LogLevel level, IntPtr msg) + { + string message = LaxUtf8Marshaler.FromNative(msg); + Handler(level, message); + } + } +} diff --git a/LibGit2Sharp/LogEntry.cs b/LibGit2Sharp/LogEntry.cs new file mode 100644 index 000000000..d7aadc06c --- /dev/null +++ b/LibGit2Sharp/LogEntry.cs @@ -0,0 +1,18 @@ +namespace LibGit2Sharp +{ + /// + /// An entry in a file's commit history. + /// + public sealed class LogEntry + { + /// + /// The file's path relative to the repository's root. + /// + public string Path { get; internal set; } + + /// + /// The commit in which the file was created or changed. + /// + public Commit Commit { get; internal set; } + } +} diff --git a/LibGit2Sharp/LogLevel.cs b/LibGit2Sharp/LogLevel.cs new file mode 100644 index 000000000..665c6afd5 --- /dev/null +++ b/LibGit2Sharp/LogLevel.cs @@ -0,0 +1,45 @@ +namespace LibGit2Sharp +{ + /// + /// Available logging levels. When logging is enabled at a particular + /// level, callers will be provided logging at the given level and all + /// lower levels. + /// + public enum LogLevel + { + /// + /// No logging will be provided. + /// + None = 0, + + /// + /// Severe errors that may impact the program's execution. + /// + Fatal = 1, + + /// + /// Errors that do not impact the program's execution. + /// + Error = 2, + + /// + /// Warnings that suggest abnormal data. + /// + Warning = 3, + + /// + /// Informational messages about program execution. + /// + Info = 4, + + /// + /// Detailed data that allows for debugging. + /// + Debug = 5, + + /// + /// Tracing is exceptionally detailed debugging data. + /// + Trace = 6, + } +} diff --git a/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs b/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs new file mode 100644 index 000000000..b0d7cfc1d --- /dev/null +++ b/LibGit2Sharp/MergeAndCheckoutOptionsBase.cs @@ -0,0 +1,74 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Options controlling the behavior of things that do a merge and then + /// check out the merge results (eg: merge, revert, cherry-pick). + /// + public abstract class MergeAndCheckoutOptionsBase : MergeOptionsBase, IConvertableToGitCheckoutOpts + { + /// + /// Initializes a new instance of the class. + /// + /// Default behavior: + /// A fast-forward merge will be performed if possible, unless the merge.ff configuration option is set. + /// A merge commit will be committed, if one was created. + /// Merge will attempt to find renames. + /// + /// + public MergeAndCheckoutOptionsBase() + { + CommitOnSuccess = true; + } + + /// + /// The Flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. + /// + public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } + + /// + /// Commit the merge if the merge is successful and this is a non-fast-forward merge. + /// If this is a fast-forward merge, then there is no merge commit and this option + /// will not affect the merge. + /// + public bool CommitOnSuccess { get; set; } + + /// + /// How conflicting index entries should be written out during checkout. + /// + public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } + + /// + /// Delegate that the checkout will report progress through. + /// + public CheckoutProgressHandler OnCheckoutProgress { get; set; } + + /// + /// Delegate that checkout will notify callers of + /// certain conditions. The conditions that are reported is + /// controlled with the CheckoutNotifyFlags property. + /// + public CheckoutNotifyHandler OnCheckoutNotify { get; set; } + + #region IConvertableToGitCheckoutOpts + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return CheckoutStrategy.GIT_CHECKOUT_SAFE | + GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); + } + } + + #endregion + } +} diff --git a/LibGit2Sharp/MergeConflictException.cs b/LibGit2Sharp/MergeConflictException.cs deleted file mode 100644 index 69ce3d6b6..000000000 --- a/LibGit2Sharp/MergeConflictException.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Runtime.Serialization; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// The exception that is thrown when a merge cannot be performed because - /// of a conflicting change. - /// - [Serializable] - public class MergeConflictException : LibGit2SharpException - { - /// - /// Initializes a new instance of the class. - /// - public MergeConflictException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// A message that describes the error. - public MergeConflictException(string message) - : base(message) - { - } - - /// - /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. - /// - /// The error message that explains the reason for the exception. - /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. - public MergeConflictException(string message, Exception innerException) - : base(message, innerException) - { - } - - /// - /// Initializes a new instance of the class with a serialized data. - /// - /// The that holds the serialized object data about the exception being thrown. - /// The that contains contextual information about the source or destination. - protected MergeConflictException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - - internal MergeConflictException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) - { - } - } -} diff --git a/LibGit2Sharp/MergeFetchHeadNotFoundException.cs b/LibGit2Sharp/MergeFetchHeadNotFoundException.cs index ff4c9cdb6..d7d761c1d 100644 --- a/LibGit2Sharp/MergeFetchHeadNotFoundException.cs +++ b/LibGit2Sharp/MergeFetchHeadNotFoundException.cs @@ -1,21 +1,23 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; -using LibGit2Sharp.Core; +#endif namespace LibGit2Sharp { /// /// The exception that is thrown when the ref to merge with was as part of a pull operation not fetched. /// +#if NETFRAMEWORK [Serializable] +#endif public class MergeFetchHeadNotFoundException : NotFoundException { /// /// Initializes a new instance of the class. /// public MergeFetchHeadNotFoundException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +25,16 @@ public MergeFetchHeadNotFoundException() /// A message that describes the error. public MergeFetchHeadNotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public MergeFetchHeadNotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +43,9 @@ public MergeFetchHeadNotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public MergeFetchHeadNotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +53,7 @@ public MergeFetchHeadNotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected MergeFetchHeadNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/MergeHead.cs b/LibGit2Sharp/MergeHead.cs index 2ad508179..b548b254a 100644 --- a/LibGit2Sharp/MergeHead.cs +++ b/LibGit2Sharp/MergeHead.cs @@ -15,8 +15,7 @@ protected MergeHead() internal MergeHead(Repository repo, ObjectId targetId, int index) : base(repo, new DirectReference(string.Format(CultureInfo.InvariantCulture, "MERGE_HEAD[{0}]", index), repo, targetId), r => r.CanonicalName) - { - } + { } /// /// Gets the that this merge head points to. diff --git a/LibGit2Sharp/MergeOptions.cs b/LibGit2Sharp/MergeOptions.cs index b88cd0ca3..b57d955e4 100644 --- a/LibGit2Sharp/MergeOptions.cs +++ b/LibGit2Sharp/MergeOptions.cs @@ -1,12 +1,11 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; +using System; namespace LibGit2Sharp { /// /// Options controlling Merge behavior. /// - public sealed class MergeOptions : IConvertableToGitCheckoutOpts + public sealed class MergeOptions : MergeAndCheckoutOptionsBase { /// /// Initializes a new instance of the class. @@ -18,89 +17,12 @@ public sealed class MergeOptions : IConvertableToGitCheckoutOpts /// /// public MergeOptions() - { - CommitOnSuccess = true; - - FindRenames = true; - // TODO: libgit2 should provide reasonable defaults for these - // values, but it currently does not. - RenameThreshold = 50; - TargetLimit = 200; - } - - /// - /// The Flags specifying what conditions are - /// reported through the OnCheckoutNotify delegate. - /// - public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } - - /// - /// Commit the merge if the merge is successful and this is a non-fast-forward merge. - /// If this is a fast-forward merge, then there is no merge commit and this option - /// will not affect the merge. - /// - public bool CommitOnSuccess { get; set; } + { } /// /// The type of merge to perform. /// public FastForwardStrategy FastForwardStrategy { get; set; } - - /// - /// How conflicting index entries should be written out during checkout. - /// - public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } - - /// - /// Find renames. Default is true. - /// - public bool FindRenames { get; set; } - - /// - /// Similarity to consider a file renamed. - /// - public int RenameThreshold; - - /// - /// Maximum similarity sources to examine (overrides - /// 'merge.renameLimit' config (default 200) - /// - public int TargetLimit; - - /// - /// How to handle conflicts encountered during a merge. - /// - public MergeFileFavor MergeFileFavor { get; set; } - - /// - /// Delegate that the checkout will report progress through. - /// - public CheckoutProgressHandler OnCheckoutProgress { get; set; } - - /// - /// Delegate that checkout will notify callers of - /// certain conditions. The conditions that are reported is - /// controlled with the CheckoutNotifyFlags property. - /// - public CheckoutNotifyHandler OnCheckoutNotify { get; set; } - - #region IConvertableToGitCheckoutOpts - - CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() - { - return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); - } - - CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy - { - get - { - return CheckoutStrategy.GIT_CHECKOUT_SAFE | - GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); - } - } - - #endregion } /// @@ -118,48 +40,11 @@ public enum FastForwardStrategy /// /// Do not fast-forward. Always creates a merge commit. /// - NoFastFoward = 1, /* GIT_MERGE_NO_FASTFORWARD */ + NoFastForward = 1, /* GIT_MERGE_NO_FASTFORWARD */ /// /// Only perform fast-forward merges. /// FastForwardOnly = 2, /* GIT_MERGE_FASTFORWARD_ONLY */ } - - /// - /// Enum specifying how merge should deal with conflicting regions - /// of the files. - /// - public enum MergeFileFavor - { - /// - /// When a region of a file is changed in both branches, a conflict - /// will be recorded in the index so that the checkout operation can produce - /// a merge file with conflict markers in the working directory. - /// This is the default. - /// - Normal = 0, - - /// - /// When a region of a file is changed in both branches, the file - /// created in the index will contain the "ours" side of any conflicting - /// region. The index will not record a conflict. - /// - Ours = 1, - - /// - /// When a region of a file is changed in both branches, the file - /// created in the index will contain the "theirs" side of any conflicting - /// region. The index will not record a conflict. - /// - Theirs = 2, - - /// - /// When a region of a file is changed in both branches, the file - /// created in the index will contain each unique line from each side, - /// which has the result of combining both files. The index will not - /// record a conflict. - /// - Union = 3, - } } diff --git a/LibGit2Sharp/MergeOptionsBase.cs b/LibGit2Sharp/MergeOptionsBase.cs new file mode 100644 index 000000000..85e930ab1 --- /dev/null +++ b/LibGit2Sharp/MergeOptionsBase.cs @@ -0,0 +1,96 @@ +namespace LibGit2Sharp +{ + /// + /// Options controlling the behavior of actions that use merge (merge + /// proper, cherry-pick, revert) + /// + public abstract class MergeOptionsBase + { + /// + /// Initializes a new instance of the class. + /// The default behavior is to attempt to find renames. + /// + protected MergeOptionsBase() + { + FindRenames = true; + RenameThreshold = 50; + TargetLimit = 200; + } + + /// + /// Find renames. Default is true. + /// + public bool FindRenames { get; set; } + + /// + /// If set, do not create or return conflict entries, but stop and return + /// an error result after finding the first conflict. + /// + public bool FailOnConflict { get; set; } + + /// + /// Do not write the Resolve Undo Cache extension on the generated index. This can + /// be useful when no merge resolution will be presented to the user (e.g. a server-side + /// merge attempt). + /// + public bool SkipReuc { get; set; } + + /// + /// Similarity to consider a file renamed. + /// + public int RenameThreshold; + + /// + /// Maximum similarity sources to examine (overrides + /// 'merge.renameLimit' config (default 200) + /// + public int TargetLimit; + + /// + /// How to handle conflicts encountered during a merge. + /// + public MergeFileFavor MergeFileFavor { get; set; } + + /// + /// Ignore changes in amount of whitespace + /// + public bool IgnoreWhitespaceChange { get; set; } + } + + /// + /// Enum specifying how merge should deal with conflicting regions + /// of the files. + /// + public enum MergeFileFavor + { + /// + /// When a region of a file is changed in both branches, a conflict + /// will be recorded in the index so that the checkout operation can produce + /// a merge file with conflict markers in the working directory. + /// This is the default. + /// + Normal = 0, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain the "ours" side of any conflicting + /// region. The index will not record a conflict. + /// + Ours = 1, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain the "theirs" side of any conflicting + /// region. The index will not record a conflict. + /// + Theirs = 2, + + /// + /// When a region of a file is changed in both branches, the file + /// created in the index will contain each unique line from each side, + /// which has the result of combining both files. The index will not + /// record a conflict. + /// + Union = 3, + } +} diff --git a/LibGit2Sharp/MergeResult.cs b/LibGit2Sharp/MergeResult.cs index e5cd91c71..6c850c639 100644 --- a/LibGit2Sharp/MergeResult.cs +++ b/LibGit2Sharp/MergeResult.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Class to report the result of a merge. diff --git a/LibGit2Sharp/MergeTreeOptions.cs b/LibGit2Sharp/MergeTreeOptions.cs new file mode 100644 index 000000000..a9eea97eb --- /dev/null +++ b/LibGit2Sharp/MergeTreeOptions.cs @@ -0,0 +1,18 @@ +namespace LibGit2Sharp +{ + /// + /// Options controlling the behavior of two trees being merged. + /// + public sealed class MergeTreeOptions : MergeOptionsBase + { + /// + /// Initializes a new instance of the class. + /// + /// Default behavior: + /// Merge will attempt to find renames. + /// + /// + public MergeTreeOptions() + { } + } +} diff --git a/LibGit2Sharp/MergeTreeResult.cs b/LibGit2Sharp/MergeTreeResult.cs new file mode 100644 index 000000000..c871c6278 --- /dev/null +++ b/LibGit2Sharp/MergeTreeResult.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; + +namespace LibGit2Sharp +{ + /// + /// The results of a merge of two trees. + /// + public class MergeTreeResult + { + /// + /// Needed for mocking purposes. + /// + protected MergeTreeResult() + { } + + internal MergeTreeResult(IEnumerable conflicts) + { + this.Status = MergeTreeStatus.Conflicts; + this.Conflicts = conflicts; + } + + internal MergeTreeResult(Tree tree) + { + this.Status = MergeTreeStatus.Succeeded; + this.Tree = tree; + this.Conflicts = new List(); + } + + /// + /// The status of the merge. + /// + public virtual MergeTreeStatus Status { get; private set; } + + /// + /// The resulting tree of the merge. + /// This will return null if the merge has been unsuccessful due to conflicts. + /// + public virtual Tree Tree { get; private set; } + + /// + /// The resulting conflicts from the merge. + /// This will return null if the merge was successful and there were no conflicts. + /// + public virtual IEnumerable Conflicts { get; private set; } + } + + /// + /// The status of what happened as a result of a merge. + /// + public enum MergeTreeStatus + { + /// + /// Merge succeeded. + /// + Succeeded, + + /// + /// Merge resulted in conflicts. + /// + Conflicts, + } +} diff --git a/LibGit2Sharp/NameConflictException.cs b/LibGit2Sharp/NameConflictException.cs index 16d1bf091..0517f2550 100644 --- a/LibGit2Sharp/NameConflictException.cs +++ b/LibGit2Sharp/NameConflictException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// /// The exception that is thrown when a reference, a remote, a submodule... with the same name already exists in the repository /// +#if NETFRAMEWORK [Serializable] - public class NameConflictException : LibGit2SharpException +#endif + public class NameConflictException : NativeException { /// /// Initializes a new instance of the class. /// public NameConflictException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public NameConflictException() /// A message that describes the error. public NameConflictException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NameConflictException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public NameConflictException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NameConflictException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +54,19 @@ public NameConflictException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NameConflictException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal NameConflictException(string message, GitErrorCategory category) + : base(message, category) + { } - internal NameConflictException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.Exists; + } } } } diff --git a/LibGit2Sharp/NativeException.cs b/LibGit2Sharp/NativeException.cs new file mode 100644 index 000000000..66dc03c57 --- /dev/null +++ b/LibGit2Sharp/NativeException.cs @@ -0,0 +1,49 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// An exception thrown that corresponds to a libgit2 (native library) error. + /// +#if NETFRAMEWORK + [Serializable] +#endif + public abstract class NativeException : LibGit2SharpException + { + /// + /// Needed for mocking purposes. + /// + protected NativeException() + { } + + internal NativeException(string message) + : base(message) + { } + + internal NativeException(string message, Exception innerException) + : base(message, innerException) + { } + + internal NativeException(string format, params object[] args) + : base(format, args) + { + } + +#if NETFRAMEWORK + internal NativeException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal NativeException(string message, GitErrorCategory category) : this(message) + { + Data.Add("libgit2.category", (int)category); + } + + internal abstract GitErrorCode ErrorCode { get; } + } +} diff --git a/LibGit2Sharp/Network.cs b/LibGit2Sharp/Network.cs index 09b2f2dfc..ba0a33144 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; @@ -46,24 +47,72 @@ public virtual RemoteCollection Remotes /// /// /// The to list from. - /// The optional used to connect to remote repository. /// The references in the repository. - public virtual IEnumerable ListReferences(Remote remote, CredentialsHandler credentialsProvider = null) + public virtual IEnumerable ListReferences(Remote remote) { Ensure.ArgumentNotNull(remote, "remote"); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) - { - if (credentialsProvider != null) - { - var callbacks = new RemoteCallbacks(null, null, null, credentialsProvider); - GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); - } + return ListReferencesInternal(remote.Url, null, new ProxyOptions()); + } - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); - return Proxy.git_remote_ls(repository, remoteHandle); - } + /// + /// List references in a repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The to list from. + /// Options for connecting through a proxy. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, ProxyOptions proxyOptions) + { + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, null, proxyOptions); + } + + /// + /// List references in a repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The to list from. + /// The used to connect to remote repository. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, CredentialsHandler credentialsProvider) + { + Ensure.ArgumentNotNull(remote, "remote"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); + + return ListReferencesInternal(remote.Url, credentialsProvider, new ProxyOptions()); + } + + /// + /// List references in a repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The to list from. + /// The used to connect to remote repository. + /// Options for connecting through a proxy. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + { + Ensure.ArgumentNotNull(remote, "remote"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); + + return ListReferencesInternal(remote.Url, credentialsProvider, proxyOptions); } /// @@ -77,85 +126,135 @@ public virtual IEnumerable ListReferences(Remote remote, Creden /// /// The url to list from. /// The references in the remote repository. - public virtual IEnumerable ListReferences(string url) + public virtual IEnumerable ListReferences(string url) { Ensure.ArgumentNotNull(url, "url"); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_anonymous(repository.Handle, url, null)) - { - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); - return Proxy.git_remote_ls(repository, remoteHandle); - } + return ListReferencesInternal(url, null, new ProxyOptions()); } - static void DoFetch(RemoteSafeHandle remoteHandle, FetchOptions options, Signature signature, string logMessage) + /// + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The url to list from. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, ProxyOptions proxyOptions) { - if (options == null) - { - options = new FetchOptions(); - } - - if (options.TagFetchMode.HasValue) - { - Proxy.git_remote_set_autotag(remoteHandle, options.TagFetchMode.Value); - } + Ensure.ArgumentNotNull(url, "url"); - var callbacks = new RemoteCallbacks(options); - GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); + return ListReferencesInternal(url, null, proxyOptions); + } - // It is OK to pass the reference to the GitCallbacks directly here because libgit2 makes a copy of - // the data in the git_remote_callbacks structure. If, in the future, libgit2 changes its implementation - // to store a reference to the git_remote_callbacks structure this would introduce a subtle bug - // where the managed layer could move the git_remote_callbacks to a different location in memory, - // but libgit2 would still reference the old address. - // - // Also, if GitRemoteCallbacks were a class instead of a struct, we would need to guard against - // GC occuring in between setting the remote callbacks and actual usage in one of the functions afterwords. - Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); + /// + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// + /// + /// The url to list from. + /// The used to connect to remote repository. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, CredentialsHandler credentialsProvider) + { + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); - Proxy.git_remote_fetch(remoteHandle, signature, logMessage); + return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); } /// - /// Fetch from the . + /// List references in a remote repository. + /// + /// When the remote tips are ahead of the local ones, the retrieved + /// s may point to non existing + /// s in the local repository. In that + /// case, will return null. + /// /// - /// The remote to fetch - /// controlling fetch behavior - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, FetchOptions options = null, - Signature signature = null, - string logMessage = null) + /// The url to list from. + /// The used to connect to remote repository. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) { - Ensure.ArgumentNotNull(remote, "remote"); + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); + + return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + } - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) + private IEnumerable ListReferencesInternal(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + { + proxyOptions ??= new(); + + using RemoteHandle remoteHandle = BuildRemoteHandle(repository.Handle, url); + using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); + + GitRemoteCallbacks gitCallbacks = new GitRemoteCallbacks { version = 1 }; + + if (credentialsProvider != null) { - DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); + var callbacks = new RemoteCallbacks(credentialsProvider); + gitCallbacks = callbacks.GenerateCallbacks(); } + + var gitProxyOptions = proxyOptionsWrapper.Options; + + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref gitProxyOptions); + return Proxy.git_remote_ls(repository, remoteHandle); + } + + static RemoteHandle BuildRemoteHandle(RepositoryHandle repoHandle, string url) + { + Debug.Assert(repoHandle != null && !repoHandle.IsInvalid); + Debug.Assert(url != null); + + RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repoHandle, url); + Debug.Assert(remoteHandle != null && !remoteHandle.IsInvalid); + + return remoteHandle; } /// - /// Fetch from the , using custom refspecs. + /// Fetch from a url with a set of fetch refspecs /// - /// The remote to fetch - /// Refspecs to use, replacing the remote's fetch refspecs - /// controlling fetch behavior - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOptions options = null, - Signature signature = null, - string logMessage = null) + /// The url to fetch from + /// The list of resfpecs to use + public virtual void Fetch(string url, IEnumerable refspecs) { - Ensure.ArgumentNotNull(remote, "remote"); - Ensure.ArgumentNotNull(refspecs, "refspecs"); + Fetch(url, refspecs, null, null); + } - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) - { - Proxy.git_remote_set_fetch_refspecs(remoteHandle, refspecs); + /// + /// Fetch from a url with a set of fetch refspecs + /// + /// The url to fetch from + /// The list of resfpecs to use + /// controlling fetch behavior + public virtual void Fetch(string url, IEnumerable refspecs, FetchOptions options) + { + Fetch(url, refspecs, options, null); + } - DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); - } + /// + /// Fetch from a url with a set of fetch refspecs + /// + /// The url to fetch from + /// The list of resfpecs to use + /// Message to use when updating the reflog. + public virtual void Fetch(string url, IEnumerable refspecs, string logMessage) + { + Fetch(url, refspecs, null, logMessage); } /// @@ -164,26 +263,106 @@ public virtual void Fetch(Remote remote, IEnumerable refspecs, FetchOpti /// The url to fetch from /// The list of resfpecs to use /// controlling fetch behavior - /// Identity for use when updating the reflog. /// Message to use when updating the reflog. public virtual void Fetch( string url, IEnumerable refspecs, - FetchOptions options = null, - Signature signature = null, - string logMessage = null) + FetchOptions options, + string logMessage) { Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(refspecs, "refspecs"); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_create_anonymous(repository.Handle, url, null)) + Commands.Fetch(repository, url, refspecs, options, logMessage); + } + + /// + /// Push the specified branch to its tracked branch on the remote. + /// + /// The branch to push. + /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. + public virtual void Push( + Branch branch) + { + Push(new[] { branch }); + } + /// + /// Push the specified branch to its tracked branch on the remote. + /// + /// The branch to push. + /// controlling push behavior + /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. + public virtual void Push( + Branch branch, + PushOptions pushOptions) + { + Push(new[] { branch }, pushOptions); + } + + /// + /// Push the specified branches to their tracked branches on the remote. + /// + /// The branches to push. + /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. + public virtual void Push( + IEnumerable branches) + { + Push(branches, null); + } + + /// + /// Push the specified branches to their tracked branches on the remote. + /// + /// The branches to push. + /// controlling push behavior + /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. + public virtual void Push( + IEnumerable branches, + PushOptions pushOptions) + { + var enumeratedBranches = branches as IList ?? branches.ToList(); + + foreach (var branch in enumeratedBranches) { - Proxy.git_remote_set_fetch_refspecs(remoteHandle, refspecs); + if (string.IsNullOrEmpty(branch.UpstreamBranchCanonicalName)) + { + throw new LibGit2SharpException("The branch '{0}' (\"{1}\") that you are trying to push does not track an upstream branch.", + branch.FriendlyName, branch.CanonicalName); + } + } - DoFetch(remoteHandle, options, signature.OrDefault(repository.Config), logMessage); + foreach (var branch in enumeratedBranches) + { + using (var remote = repository.Network.Remotes.RemoteForName(branch.RemoteName)) + { + Push(remote, string.Format( + CultureInfo.InvariantCulture, + "{0}:{1}", branch.CanonicalName, branch.UpstreamBranchCanonicalName), pushOptions); + } } } + /// + /// Push the objectish to the destination reference on the . + /// + /// The to push to. + /// The source objectish to push. + /// The reference to update on the remote. + public virtual void Push( + Remote remote, + string objectish, + string destinationSpec) + { + Ensure.ArgumentNotNull(objectish, "objectish"); + Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); + + Push(remote, + string.Format(CultureInfo.InvariantCulture, + "{0}:{1}", + objectish, + destinationSpec)); + } + /// /// Push the objectish to the destination reference on the . /// @@ -191,43 +370,58 @@ public virtual void Fetch( /// The source objectish to push. /// The reference to update on the remote. /// controlling push behavior - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. public virtual void Push( Remote remote, string objectish, string destinationSpec, - PushOptions pushOptions = null, - Signature signature = null, - string logMessage = null) + PushOptions pushOptions) { - Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(objectish, "objectish"); - Ensure.ArgumentNotNullOrEmptyString(destinationSpec, destinationSpec); - - Push(remote, string.Format(CultureInfo.InvariantCulture, - "{0}:{1}", objectish, destinationSpec), pushOptions, signature, logMessage); + Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); + + Push(remote, + string.Format(CultureInfo.InvariantCulture, + "{0}:{1}", + objectish, + destinationSpec), + pushOptions); } + /// + /// Push specified reference to the . + /// + /// The to push to. + /// The pushRefSpec to push. + public virtual void Push(Remote remote, string pushRefSpec) + { + Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); + + Push(remote, new[] { pushRefSpec }); + } /// /// Push specified reference to the . /// /// The to push to. /// The pushRefSpec to push. /// controlling push behavior - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. public virtual void Push( Remote remote, string pushRefSpec, - PushOptions pushOptions = null, - Signature signature = null, - string logMessage = null) + PushOptions pushOptions) { - Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); - Push(remote, new[] { pushRefSpec }, pushOptions, signature, logMessage); + Push(remote, new[] { pushRefSpec }, pushOptions); + } + + /// + /// Push specified references to the . + /// + /// The to push to. + /// The pushRefSpecs to push. + public virtual void Push(Remote remote, IEnumerable pushRefSpecs) + { + Push(remote, pushRefSpecs, null); } /// @@ -236,26 +430,11 @@ public virtual void Push( /// The to push to. /// The pushRefSpecs to push. /// controlling push behavior - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - public virtual void Push( - Remote remote, - IEnumerable pushRefSpecs, - PushOptions pushOptions = null, - Signature signature = null, - string logMessage = null) + public virtual void Push(Remote remote, IEnumerable pushRefSpecs, PushOptions pushOptions) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); - // The following local variables are protected from garbage collection - // by a GC.KeepAlive call at the end of the method. Otherwise, - // random crashes during push progress reporting could occur. - PushTransferCallbacks pushTransferCallbacks; - PackbuilderCallbacks packBuilderCallbacks; - NativeMethods.git_push_transfer_progress pushProgress; - NativeMethods.git_packbuilder_progress packBuilderProgress; - // Return early if there is nothing to push. if (!pushRefSpecs.Any()) { @@ -267,90 +446,30 @@ public virtual void Push( pushOptions = new PushOptions(); } - PushCallbacks pushStatusUpdates = new PushCallbacks(pushOptions.OnPushStatusError); - // Load the remote. - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true)) + + // Create a git options wrapper so managed strings are disposed. + using (var pushOptionsWrapper = new GitPushOptionsWrapper()) { - var callbacks = new RemoteCallbacks(null, null, null, pushOptions); + var callbacks = new RemoteCallbacks(pushOptions); GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - Proxy.git_remote_set_callbacks(remoteHandle, ref gitCallbacks); - try - { - Proxy.git_remote_connect(remoteHandle, GitDirection.Push); - - // Perform the actual push. - using (PushSafeHandle pushHandle = Proxy.git_push_new(remoteHandle)) - { - pushTransferCallbacks = new PushTransferCallbacks(pushOptions.OnPushTransferProgress); - packBuilderCallbacks = new PackbuilderCallbacks(pushOptions.OnPackBuilderProgress); - - pushProgress = pushTransferCallbacks.GenerateCallback(); - packBuilderProgress = packBuilderCallbacks.GenerateCallback(); - - Proxy.git_push_set_callbacks(pushHandle, pushProgress, packBuilderProgress); - - // Set push options. - Proxy.git_push_set_options(pushHandle, - new GitPushOptions() - { - PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism - }); - - // Add refspecs. - foreach (string pushRefSpec in pushRefSpecs) - { - Proxy.git_push_add_refspec(pushHandle, pushRefSpec); - } - - Proxy.git_push_finish(pushHandle); - - if (!Proxy.git_push_unpack_ok(pushHandle)) - { - throw new LibGit2SharpException("Push failed - remote did not successfully unpack."); - } - - Proxy.git_push_status_foreach(pushHandle, pushStatusUpdates.Callback); - Proxy.git_push_update_tips(pushHandle, signature.OrDefault(repository.Config), logMessage); - } - } - finally + var gitPushOptions = pushOptionsWrapper.Options; + gitPushOptions.PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism; + gitPushOptions.RemoteCallbacks = gitCallbacks; + gitPushOptions.ProxyOptions = pushOptions.ProxyOptions.CreateGitProxyOptions(); + + // If there are custom headers, create a managed string array. + if (pushOptions.CustomHeaders != null && pushOptions.CustomHeaders.Length > 0) { - Proxy.git_remote_disconnect(remoteHandle); + gitPushOptions.CustomHeaders = GitStrArrayManaged.BuildFrom(pushOptions.CustomHeaders); } - } - GC.KeepAlive(pushProgress); - GC.KeepAlive(packBuilderProgress); - GC.KeepAlive(pushTransferCallbacks); - GC.KeepAlive(packBuilderCallbacks); - } - - /// - /// Pull changes from the configured upstream remote and branch into the branch pointed at by HEAD. - /// - /// If the merge is a non-fast forward merge that generates a merge commit, the of who made the merge. - /// Specifies optional parameters controlling merge behavior of pull; if null, the defaults are used. - public virtual MergeResult Pull(Signature merger, PullOptions options) - { - Ensure.ArgumentNotNull(merger, "merger"); - Ensure.ArgumentNotNull(options, "options"); - - Branch currentBranch = repository.Head; - - if(!currentBranch.IsTracking) - { - throw new LibGit2SharpException("There is no tracking information for the current branch."); + Proxy.git_remote_push(remoteHandle, + pushRefSpecs, + gitPushOptions); } - - if (currentBranch.Remote == null) - { - throw new LibGit2SharpException("No upstream remote for the current branch."); - } - - Fetch(currentBranch.Remote, options.FetchOptions); - return repository.MergeFetchHeads(merger, options.MergeOptions); } /// @@ -362,50 +481,10 @@ internal virtual IEnumerable FetchHeads { int i = 0; - return Proxy.git_repository_fetchhead_foreach( - repository.Handle, - (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++)); - } - } - - /// - /// Helper class to handle callbacks during push. - /// - private class PushCallbacks - { - readonly PushStatusErrorHandler onError; - - public PushCallbacks(PushStatusErrorHandler onError) - { - this.onError = onError; - } - - public int Callback(IntPtr referenceNamePtr, IntPtr msgPtr, IntPtr payload) - { - // Exit early if there is no callback. - if (onError == null) - { - return 0; - } - - // The reference name pointer should never be null - if it is, - // this indicates a bug somewhere (libgit2, server, etc). - if (referenceNamePtr == IntPtr.Zero) - { - Proxy.giterr_set_str(GitErrorCategory.Invalid, "Not expecting null for reference name in push status."); - return -1; - } - - // Only report updates where there is a message - indicating - // that there was an error. - if (msgPtr != IntPtr.Zero) - { - string referenceName = LaxUtf8Marshaler.FromNative(referenceNamePtr); - string msg = LaxUtf8Marshaler.FromNative(msgPtr); - onError(new PushStatusError(referenceName, msg)); - } + Func resultSelector = + (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++); - return 0; + return Proxy.git_repository_fetchhead_foreach(repository.Handle, resultSelector); } } } diff --git a/LibGit2Sharp/NetworkExtensions.cs b/LibGit2Sharp/NetworkExtensions.cs deleted file mode 100644 index e9e6f75a2..000000000 --- a/LibGit2Sharp/NetworkExtensions.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class NetworkExtensions - { - /// - /// Push the specified branch to its tracked branch on the remote. - /// - /// The being worked with. - /// The branch to push. - /// controlling push behavior - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - Branch branch, - PushOptions pushOptions = null) - { - network.Push(new[] { branch }, pushOptions); - } - - /// - /// Push the specified branches to their tracked branches on the remote. - /// - /// The being worked with. - /// The branches to push. - /// controlling push behavior - /// Throws if either the Remote or the UpstreamBranchCanonicalName is not set. - public static void Push( - this Network network, - IEnumerable branches, - PushOptions pushOptions = null) - { - var enumeratedBranches = branches as IList ?? branches.ToList(); - - foreach (var branch in enumeratedBranches) - { - if (string.IsNullOrEmpty(branch.UpstreamBranchCanonicalName)) - { - throw new LibGit2SharpException( - string.Format( - CultureInfo.InvariantCulture, - "The branch '{0}' (\"{1}\") that you are trying to push does not track an upstream branch.", - branch.Name, branch.CanonicalName)); - } - } - - foreach (var branch in enumeratedBranches) - { - network.Push(branch.Remote, string.Format( - CultureInfo.InvariantCulture, - "{0}:{1}", branch.CanonicalName, branch.UpstreamBranchCanonicalName), pushOptions); - } - } - } -} diff --git a/LibGit2Sharp/NonFastForwardException.cs b/LibGit2Sharp/NonFastForwardException.cs index 2cf4ccd93..d8ed8f474 100644 --- a/LibGit2Sharp/NonFastForwardException.cs +++ b/LibGit2Sharp/NonFastForwardException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -8,15 +10,16 @@ namespace LibGit2Sharp /// The exception that is thrown when push cannot be performed /// against the remote without losing commits. /// +#if NETFRAMEWORK [Serializable] - public class NonFastForwardException : LibGit2SharpException +#endif + public class NonFastForwardException : NativeException { /// /// Initializes a new instance of the class. /// public NonFastForwardException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +27,16 @@ public NonFastForwardException() /// A message that describes the error. public NonFastForwardException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NonFastForwardException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,9 +45,9 @@ public NonFastForwardException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NonFastForwardException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -44,12 +55,19 @@ public NonFastForwardException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NonFastForwardException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal NonFastForwardException(string message, GitErrorCategory category) + : base(message, category) + { } - internal NonFastForwardException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.NonFastForward; + } } } } diff --git a/LibGit2Sharp/NotFoundException.cs b/LibGit2Sharp/NotFoundException.cs index c2a32ed00..f282c4340 100644 --- a/LibGit2Sharp/NotFoundException.cs +++ b/LibGit2Sharp/NotFoundException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// /// The exception that is thrown attempting to reference a resource that does not exist. /// +#if NETFRAMEWORK [Serializable] - public class NotFoundException : LibGit2SharpException +#endif + public class NotFoundException : NativeException { /// /// Initializes a new instance of the class. /// public NotFoundException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public NotFoundException() /// A message that describes the error. public NotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public NotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public NotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public NotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +54,19 @@ public NotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected NotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal NotFoundException(string message, GitErrorCategory category) + : base(message, category) + { } - internal NotFoundException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.NotFound; + } } } } diff --git a/LibGit2Sharp/Note.cs b/LibGit2Sharp/Note.cs index 89a4eb65b..2ffc89690 100644 --- a/LibGit2Sharp/Note.cs +++ b/LibGit2Sharp/Note.cs @@ -12,6 +12,9 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Note : IEquatable { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.BlobId, x => x.TargetObjectId, x => x.Namespace); + /// /// Needed for mocking purposes. /// @@ -47,7 +50,7 @@ private Note(ObjectId blobId, string message, ObjectId targetObjectId, string @n /// public virtual ObjectId TargetObjectId { get; private set; } - internal static Note BuildFromPtr(NoteSafeHandle note, string @namespace, ObjectId targetObjectId) + internal static Note BuildFromPtr(NoteHandle note, string @namespace, ObjectId targetObjectId) { ObjectId oid = Proxy.git_note_id(note); string message = Proxy.git_note_message(note); @@ -55,14 +58,11 @@ internal static Note BuildFromPtr(NoteSafeHandle note, string @namespace, Object return new Note(oid, message, targetObjectId, @namespace); } - private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.BlobId, x => x.TargetObjectId, x => x.Namespace); - /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as Note); @@ -114,8 +114,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "Target \"{0}\", Namespace \"{1}\": {2}", - TargetObjectId.ToString(7), Namespace, Message); + "Target \"{0}\", Namespace \"{1}\": {2}", + TargetObjectId.ToString(7), + Namespace, + Message); } } } diff --git a/LibGit2Sharp/NoteCollection.cs b/LibGit2Sharp/NoteCollection.cs index 678ffdeac..30084881d 100644 --- a/LibGit2Sharp/NoteCollection.cs +++ b/LibGit2Sharp/NoteCollection.cs @@ -65,10 +65,7 @@ public virtual string DefaultNamespace /// public virtual IEnumerable Namespaces { - get - { - return NamespaceRefs.Select(UnCanonicalizeName); - } + get { return NamespaceRefs.Select(UnCanonicalizeName); } } internal IEnumerable NamespaceRefs @@ -108,8 +105,9 @@ public virtual IEnumerable this[string @namespace] string canonicalNamespace = NormalizeToCanonicalName(@namespace); - return Proxy.git_note_foreach(repo.Handle, canonicalNamespace, - (blobId,annotatedObjId) => this[canonicalNamespace, annotatedObjId]); + return Proxy.git_note_foreach(repo.Handle, + canonicalNamespace, + (blobId, annotatedObjId) => this[canonicalNamespace, annotatedObjId]); } } @@ -125,7 +123,7 @@ public virtual IEnumerable this[string @namespace] string canonicalNamespace = NormalizeToCanonicalName(@namespace); - using (NoteSafeHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, id)) + using (NoteHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, id)) { return noteHandle == null ? null @@ -186,7 +184,7 @@ public virtual Note Add(ObjectId targetId, string message, Signature author, Sig Remove(targetId, author, committer, @namespace); - Proxy.git_note_create(repo.Handle, author, committer, canonicalNamespace, targetId, message, true); + Proxy.git_note_create(repo.Handle, canonicalNamespace, author, committer, targetId, message, true); return this[canonicalNamespace, targetId]; } @@ -214,8 +212,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/NoteCollectionExtensions.cs b/LibGit2Sharp/NoteCollectionExtensions.cs deleted file mode 100644 index 3a2f78e22..000000000 --- a/LibGit2Sharp/NoteCollectionExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class NoteCollectionExtensions - { - /// - /// Creates or updates a on the specified object, and for the given namespace. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The - /// The target , for which the note will be created. - /// The note message. - /// The namespace on which the note will be created. It can be either a canonical namespace or an abbreviated namespace ('refs/notes/myNamespace' or just 'myNamespace'). - /// The note which was just saved. - public static Note Add(this NoteCollection collection, ObjectId targetId, string message, string @namespace) - { - Signature author = collection.repo.Config.BuildSignature(DateTimeOffset.Now, true); - - return collection.Add(targetId, message, author, author, @namespace); - } - - /// - /// Deletes the note on the specified object, and for the given namespace. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The - /// The target , for which the note will be created. - /// The namespace on which the note will be removed. It can be either a canonical namespace or an abbreviated namespace ('refs/notes/myNamespace' or just 'myNamespace'). - public static void Remove(this NoteCollection collection, ObjectId targetId, string @namespace) - { - Signature author = collection.repo.Config.BuildSignature(DateTimeOffset.Now, true); - - collection.Remove(targetId, author, author, @namespace); - } - } -} diff --git a/LibGit2Sharp/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 9398855fc..1bad9c907 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -17,7 +17,7 @@ namespace LibGit2Sharp public class ObjectDatabase : IEnumerable { private readonly Repository repo; - private readonly ObjectDatabaseSafeHandle handle; + private readonly ObjectDatabaseHandle handle; /// /// Needed for mocking purposes. @@ -41,11 +41,10 @@ internal ObjectDatabase(Repository repo) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - ICollection oids = Proxy.git_odb_foreach(handle, - ptr => ptr.MarshalAs()); + ICollection oids = Proxy.git_odb_foreach(handle); return oids - .Select(gitOid => repo.Lookup(new ObjectId(gitOid))) + .Select(gitOid => repo.Lookup(gitOid)) .GetEnumerator(); } @@ -72,6 +71,20 @@ public virtual bool Contains(ObjectId objectId) return Proxy.git_odb_exists(handle, objectId); } + /// + /// Retrieves the header of a GitObject from the object database. The header contains the Size + /// and Type of the object. Note that most backends do not support reading only the header + /// of an object, so the whole object will be read and then size would be returned. + /// + /// Object Id of the queried object + /// GitObjectMetadata object instance containg object header information + public virtual GitObjectMetadata RetrieveObjectMetadata(ObjectId objectId) + { + Ensure.ArgumentNotNull(objectId, "objectId"); + + return Proxy.git_odb_read_header(handle, objectId); + } + /// /// Inserts a into the object database, created from the content of a file. /// @@ -85,14 +98,14 @@ public virtual Blob CreateBlob(string path) if (repo.Info.IsBare && !Path.IsPathRooted(path)) { - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, - "Cannot create a blob in a bare repository from a relative path ('{0}').", path)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot create a blob in a bare repository from a relative path ('{0}').", + path)); } ObjectId id = Path.IsPathRooted(path) - ? Proxy.git_blob_create_fromdisk(repo.Handle, path) - : Proxy.git_blob_create_fromfile(repo.Handle, path); + ? Proxy.git_blob_create_from_disk(repo.Handle, path) + : Proxy.git_blob_create_from_workdir(repo.Handle, path); return repo.Lookup(id); } @@ -117,10 +130,10 @@ public virtual void AddBackend(OdbBackend backend, int priority) private class Processor { private readonly Stream stream; - private readonly int? numberOfBytesToConsume; + private readonly long? numberOfBytesToConsume; private int totalNumberOfReadBytes; - public Processor(Stream stream, int? numberOfBytesToConsume) + public Processor(Stream stream, long? numberOfBytesToConsume) { this.stream = stream; this.numberOfBytesToConsume = numberOfBytesToConsume; @@ -134,11 +147,13 @@ public int Provider(IntPtr content, int max_length, IntPtr data) if (numberOfBytesToConsume.HasValue) { - int totalRemainingBytesToRead = numberOfBytesToConsume.Value - totalNumberOfReadBytes; + long totalRemainingBytesToRead = numberOfBytesToConsume.Value - totalNumberOfReadBytes; if (totalRemainingBytesToRead < max_length) { - bytesToRead = totalRemainingBytesToRead; + bytesToRead = totalRemainingBytesToRead > int.MaxValue + ? int.MaxValue + : (int)totalRemainingBytesToRead; } } @@ -149,8 +164,7 @@ public int Provider(IntPtr content, int max_length, IntPtr data) int numberOfReadBytes = stream.Read(local, 0, bytesToRead); - if (numberOfBytesToConsume.HasValue - && numberOfReadBytes == 0) + if (numberOfBytesToConsume.HasValue && numberOfReadBytes == 0) { return (int)GitErrorCode.User; } @@ -164,73 +178,163 @@ public int Provider(IntPtr content, int max_length, IntPtr data) } /// - /// Inserts a into the object database, created from the content of a stream. - /// Optionally, git filters will be applied to the content before storing it. + /// Writes an object to the object database. /// - /// The stream from which will be read the content of the blob to be created. - /// The hintpath is used to determine what git filters should be applied to the object before it can be placed to the object database. - /// The number of bytes to consume from the stream. - /// The created . - public virtual Blob CreateBlob(Stream stream, string hintpath = null, int? numberOfBytesToConsume = null) + /// The contents of the object + /// The type of object to write + public virtual ObjectId Write(byte[] data) where T : GitObject + { + return Proxy.git_odb_write(handle, data, GitObject.TypeToKindMap[typeof(T)]); + } + + /// + /// Writes an object to the object database. + /// + /// The contents of the object + /// The number of bytes to consume from the stream + /// The type of object to write + public virtual ObjectId Write(Stream stream, long numberOfBytesToConsume) where T : GitObject { Ensure.ArgumentNotNull(stream, "stream"); - // there's no need to buffer the file for filtering, so simply use a stream - if (hintpath == null && numberOfBytesToConsume.HasValue) + if (!stream.CanRead) { - return CreateBlob(stream, numberOfBytesToConsume.Value); + throw new ArgumentException("The stream cannot be read from.", nameof(stream)); } - if (!stream.CanRead) + using (var odbStream = Proxy.git_odb_open_wstream(handle, numberOfBytesToConsume, GitObject.TypeToGitKindMap[typeof(T)])) { - throw new ArgumentException("The stream cannot be read from.", "stream"); + var buffer = new byte[4 * 1024]; + long totalRead = 0; + + while (totalRead < numberOfBytesToConsume) + { + long left = numberOfBytesToConsume - totalRead; + int toRead = left < buffer.Length ? (int)left : buffer.Length; + var read = stream.Read(buffer, 0, toRead); + + if (read == 0) + { + throw new EndOfStreamException("The stream ended unexpectedly"); + } + + Proxy.git_odb_stream_write(odbStream, buffer, read); + totalRead += read; + } + + return Proxy.git_odb_stream_finalize_write(odbStream); } + } - var proc = new Processor(stream, numberOfBytesToConsume); - ObjectId id = Proxy.git_blob_create_fromchunks(repo.Handle, hintpath, proc.Provider); + /// + /// Inserts a into the object database, created from the content of a stream. + /// Optionally, git filters will be applied to the content before storing it. + /// + /// The stream from which will be read the content of the blob to be created. + /// The created . + public virtual Blob CreateBlob(Stream stream) + { + return CreateBlob(stream, null, null); + } - return repo.Lookup(id); + /// + /// Inserts a into the object database, created from the content of a stream. + /// Optionally, git filters will be applied to the content before storing it. + /// + /// The stream from which will be read the content of the blob to be created. + /// The hintpath is used to determine what git filters should be applied to the object before it can be placed to the object database. + /// The created . + public virtual Blob CreateBlob(Stream stream, string hintpath) + { + return CreateBlob(stream, hintpath, null); } /// - /// Inserts a into the object database created from the content of the stream. + /// Inserts a into the object database, created from the content of a stream. + /// Optionally, git filters will be applied to the content before storing it. /// /// The stream from which will be read the content of the blob to be created. - /// Number of bytes to consume from the stream. + /// The hintpath is used to determine what git filters should be applied to the object before it can be placed to the object database. + /// The number of bytes to consume from the stream. /// The created . - public virtual Blob CreateBlob(Stream stream, int numberOfBytesToConsume) + public virtual Blob CreateBlob(Stream stream, string hintpath, long numberOfBytesToConsume) + { + return CreateBlob(stream, hintpath, (long?)numberOfBytesToConsume); + } + + private unsafe Blob CreateBlob(Stream stream, string hintpath, long? numberOfBytesToConsume) { Ensure.ArgumentNotNull(stream, "stream"); + // there's no need to buffer the file for filtering, so simply use a stream + if (hintpath == null && numberOfBytesToConsume.HasValue) + { + return CreateBlob(stream, numberOfBytesToConsume.Value); + } + if (!stream.CanRead) { - throw new ArgumentException("The stream cannot be read from.", "stream"); + throw new ArgumentException("The stream cannot be read from.", nameof(stream)); } - using (var odbStream = Proxy.git_odb_open_wstream(handle, (UIntPtr)numberOfBytesToConsume, GitObjectType.Blob)) + IntPtr writestream_ptr = Proxy.git_blob_create_from_stream(repo.Handle, hintpath); + GitWriteStream writestream = Marshal.PtrToStructure(writestream_ptr); + + try { - var buffer = new byte[4*1024]; - int totalRead = 0; + var buffer = new byte[4 * 1024]; + long totalRead = 0; + int read = 0; - while (totalRead < numberOfBytesToConsume) + while (true) { - var left = numberOfBytesToConsume - totalRead; - var toRead = left < buffer.Length ? left : buffer.Length; - var read = stream.Read(buffer, 0, toRead); + int toRead = numberOfBytesToConsume.HasValue ? + (int)Math.Min(numberOfBytesToConsume.Value - totalRead, (long)buffer.Length) : + buffer.Length; + + if (toRead > 0) + { + read = (toRead > 0) ? stream.Read(buffer, 0, toRead) : 0; + } if (read == 0) { - throw new EndOfStreamException("The stream ended unexpectedly"); + break; + } + + fixed (byte* buffer_ptr = buffer) + { + writestream.write(writestream_ptr, (IntPtr)buffer_ptr, (UIntPtr)read); } - Proxy.git_odb_stream_write(odbStream, buffer, read); totalRead += read; } - var id = Proxy.git_odb_stream_finalize_write(odbStream); - - return repo.Lookup(id); + if (numberOfBytesToConsume.HasValue && totalRead < numberOfBytesToConsume.Value) + { + throw new EndOfStreamException("The stream ended unexpectedly"); + } } + catch (Exception) + { + writestream.free(writestream_ptr); + throw; + } + + ObjectId id = Proxy.git_blob_create_fromstream_commit(writestream_ptr); + return repo.Lookup(id); + } + + /// + /// Inserts a into the object database created from the content of the stream. + /// + /// The stream from which will be read the content of the blob to be created. + /// Number of bytes to consume from the stream. + /// The created . + public virtual Blob CreateBlob(Stream stream, long numberOfBytesToConsume) + { + var id = Write(stream, numberOfBytesToConsume); + return repo.Lookup(id); } /// @@ -260,7 +364,7 @@ public virtual Tree CreateTree(Index index) { Ensure.ArgumentNotNull(index, "index"); - var treeId = Proxy.git_tree_create_fromindex(index); + var treeId = Proxy.git_index_write_tree(index.Handle); return this.repo.Lookup(treeId); } @@ -281,9 +385,32 @@ public virtual Tree CreateTree(Index index) /// The of the to be created. /// The parents of the to be created. /// True to prettify the message, or false to leave it as is. - /// Character that lines start with to be stripped if prettifyMessage is true. /// The created . - public virtual Commit CreateCommit(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage, char? commentChar = null) + public virtual Commit CreateCommit(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage) + { + return CreateCommit(author, committer, message, tree, parents, prettifyMessage, null); + } + + /// + /// Inserts a into the object database, referencing an existing . + /// + /// Prettifing the message includes: + /// * Removing empty lines from the beginning and end. + /// * Removing trailing spaces from every line. + /// * Turning multiple consecutive empty lines between paragraphs into just one empty line. + /// * Ensuring the commit message ends with a newline. + /// * Removing every line starting with the . + /// + /// + /// The of who made the change. + /// The of who added the change to the repository. + /// The description of why a change was made to the repository. + /// The of the to be created. + /// The parents of the to be created. + /// True to prettify the message, or false to leave it as is. + /// When non null, lines starting with this character will be stripped if prettifyMessage is true. + /// The created . + public virtual Commit CreateCommit(Signature author, Signature committer, string message, Tree tree, IEnumerable parents, bool prettifyMessage, char? commentChar) { Ensure.ArgumentNotNull(message, "message"); Ensure.ArgumentDoesNotContainZeroByte(message, "message"); @@ -300,7 +427,35 @@ public virtual Commit CreateCommit(Signature author, Signature committer, string ObjectId commitId = Proxy.git_commit_create(repo.Handle, null, author, committer, message, tree, parentIds); - return repo.Lookup(commitId); + Commit commit = repo.Lookup(commitId); + Ensure.GitObjectIsNotNull(commit, commitId.Sha); + return commit; + } + + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// The raw unsigned commit + /// The signature data + /// The header field in the commit in which to store the signature + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature, string field) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, field); + } + + /// + /// Inserts a into the object database after attaching the given signature. + /// + /// This overload uses the default header field of "gpgsig" + /// + /// + /// The raw unsigned commit + /// The signature data + /// The created . + public virtual ObjectId CreateCommitWithSignature(string commitContent, string signature) + { + return Proxy.git_commit_create_with_signature(repo.Handle, commitContent, signature, null); } /// @@ -327,6 +482,34 @@ public virtual TagAnnotation CreateTagAnnotation(string name, GitObject target, return repo.Lookup(tagId); } + /// + /// Create a TAR archive of the given tree. + /// + /// The tree. + /// The archive path. + public virtual void Archive(Tree tree, string archivePath) + { + using (var output = new FileStream(archivePath, FileMode.Create)) + using (var archiver = new TarArchiver(output)) + { + Archive(tree, archiver); + } + } + + /// + /// Create a TAR archive of the given commit. + /// + /// commit. + /// The archive path. + public virtual void Archive(Commit commit, string archivePath) + { + using (var output = new FileStream(archivePath, FileMode.Create)) + using (var archiver = new TarArchiver(output)) + { + Archive(commit, archiver); + } + } + /// /// Archive the given commit. /// @@ -368,6 +551,81 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a return new HistoryDivergence(repo, one, another); } + /// + /// Performs a cherry-pick of onto commit. + /// + /// The commit to cherry-pick. + /// The commit to cherry-pick onto. + /// Which commit to consider the parent for the diff when cherry-picking a merge commit. + /// The options for the merging in the cherry-pick operation. + /// A result containing a if the cherry-pick was successful and a list of s if it is not. + public virtual MergeTreeResult CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit"); + Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto"); + + var modifiedOptions = new MergeTreeOptions(); + + // We throw away the index after looking at the conflicts, so we'll never need the REUC + // entries to be there + modifiedOptions.SkipReuc = true; + + if (options != null) + { + modifiedOptions.FailOnConflict = options.FailOnConflict; + modifiedOptions.FindRenames = options.FindRenames; + modifiedOptions.MergeFileFavor = options.MergeFileFavor; + modifiedOptions.RenameThreshold = options.RenameThreshold; + modifiedOptions.TargetLimit = options.TargetLimit; + } + + bool earlyStop; + + using (var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, modifiedOptions, out earlyStop)) + { + MergeTreeResult cherryPickResult; + + // Stopped due to FailOnConflict so there's no index or conflict list + if (earlyStop) + { + return new MergeTreeResult(Array.Empty()); + } + + if (Proxy.git_index_has_conflicts(indexHandle)) + { + List conflicts = new List(); + Conflict conflict; + using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + { + while ((conflict = Proxy.git_index_conflict_next(iterator)) != null) + { + conflicts.Add(conflict); + } + } + cherryPickResult = new MergeTreeResult(conflicts); + } + else + { + var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); + cherryPickResult = new MergeTreeResult(this.repo.Lookup(treeId)); + } + + return cherryPickResult; + } + } + + /// + /// Calculates the current shortest abbreviated + /// string representation for a . + /// + /// The which identifier should be shortened. + /// A short string representation of the . + public virtual string ShortenObjectId(GitObject gitObject) + { + var shortSha = Proxy.git_object_short_id(repo.Handle, gitObject.Id); + return shortSha; + } + /// /// Calculates the current shortest abbreviated /// string representation for a . @@ -375,22 +633,460 @@ public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit a /// The which identifier should be shortened. /// Minimum length of the shortened representation. /// A short string representation of the . - public virtual string ShortenObjectId(GitObject gitObject, int? minLength = null) + public virtual string ShortenObjectId(GitObject gitObject, int minLength) { - if (minLength.HasValue && (minLength <= 0 || minLength > ObjectId.HexSize)) + Ensure.ArgumentNotNull(gitObject, "gitObject"); + + if (minLength <= 0 || minLength > ObjectId.HexSize) { - throw new ArgumentOutOfRangeException("minLength", minLength, - string.Format("Expected value should be greater than zero and less than or equal to {0}.", ObjectId.HexSize)); + throw new ArgumentOutOfRangeException(nameof(minLength), + minLength, + string.Format("Expected value should be greater than zero and less than or equal to {0}.", + ObjectId.HexSize)); } string shortSha = Proxy.git_object_short_id(repo.Handle, gitObject.Id); - if (minLength == null || (minLength <= shortSha.Length)) + if (shortSha == null) + { + throw new LibGit2SharpException("Unable to abbreviate SHA-1 value for GitObject " + gitObject.Id); + } + + if (minLength <= shortSha.Length) { return shortSha; } - return gitObject.Sha.Substring(0, minLength.Value); + return gitObject.Sha.Substring(0, minLength); + } + + /// + /// Returns whether merging into + /// would result in merge conflicts. + /// + /// The commit wrapping the base tree to merge into. + /// The commit wrapping the tree to merge into . + /// True if the merge does not result in a conflict, false otherwise. + public virtual bool CanMergeWithoutConflict(Commit one, Commit another) + { + Ensure.ArgumentNotNull(one, "one"); + Ensure.ArgumentNotNull(another, "another"); + + var opts = new MergeTreeOptions() + { + SkipReuc = true, + FailOnConflict = true, + }; + + var result = repo.ObjectDatabase.MergeCommits(one, another, opts); + return (result.Status == MergeTreeStatus.Succeeded); + } + + /// + /// Find the best possible merge base given two s. + /// + /// The first . + /// The second . + /// The merge base or null if none found. + public virtual Commit FindMergeBase(Commit first, Commit second) + { + Ensure.ArgumentNotNull(first, "first"); + Ensure.ArgumentNotNull(second, "second"); + + return FindMergeBase(new[] { first, second }, MergeBaseFindingStrategy.Standard); + } + + /// + /// Find the best possible merge base given two or more according to the . + /// + /// The s for which to find the merge base. + /// The strategy to leverage in order to find the merge base. + /// The merge base or null if none found. + public virtual Commit FindMergeBase(IEnumerable commits, MergeBaseFindingStrategy strategy) + { + Ensure.ArgumentNotNull(commits, "commits"); + + ObjectId id; + List ids = new List(8); + int count = 0; + + foreach (var commit in commits) + { + if (commit == null) + { + throw new ArgumentException("Enumerable contains null at position: " + count.ToString(CultureInfo.InvariantCulture), nameof(commits)); + } + ids.Add(commit.Id.Oid); + count++; + } + + if (count < 2) + { + throw new ArgumentException("The enumerable must contains at least two commits.", nameof(commits)); + } + + switch (strategy) + { + case MergeBaseFindingStrategy.Standard: + id = Proxy.git_merge_base_many(repo.Handle, ids.ToArray()); + break; + + case MergeBaseFindingStrategy.Octopus: + id = Proxy.git_merge_base_octopus(repo.Handle, ids.ToArray()); + break; + + default: + throw new ArgumentException("", nameof(strategy)); + } + + return id == null ? null : repo.Lookup(id); + } + + /// + /// Perform a three-way merge of two commits, looking up their + /// commit ancestor. The returned will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The first commit + /// The second commit + /// The controlling the merge + /// The containing the merged trees and any conflicts + public virtual MergeTreeResult MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(ours, "ours"); + Ensure.ArgumentNotNull(theirs, "theirs"); + + var modifiedOptions = new MergeTreeOptions(); + + // We throw away the index after looking at the conflicts, so we'll never need the REUC + // entries to be there + modifiedOptions.SkipReuc = true; + + if (options != null) + { + modifiedOptions.FailOnConflict = options.FailOnConflict; + modifiedOptions.FindRenames = options.FindRenames; + modifiedOptions.IgnoreWhitespaceChange = options.IgnoreWhitespaceChange; + modifiedOptions.MergeFileFavor = options.MergeFileFavor; + modifiedOptions.RenameThreshold = options.RenameThreshold; + modifiedOptions.TargetLimit = options.TargetLimit; + } + + bool earlyStop; + using (var indexHandle = MergeCommits(ours, theirs, modifiedOptions, out earlyStop)) + { + MergeTreeResult mergeResult; + + // Stopped due to FailOnConflict so there's no index or conflict list + if (earlyStop) + { + return new MergeTreeResult(Array.Empty()); + } + + if (Proxy.git_index_has_conflicts(indexHandle)) + { + List conflicts = new List(); + Conflict conflict; + + using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + { + while ((conflict = Proxy.git_index_conflict_next(iterator)) != null) + { + conflicts.Add(conflict); + } + } + + mergeResult = new MergeTreeResult(conflicts); + } + else + { + var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); + mergeResult = new MergeTreeResult(this.repo.Lookup(treeId)); + } + + return mergeResult; + } + } + + /// + /// Packs all the objects in the and write a pack (.pack) and index (.idx) files for them. + /// + /// Packing options + /// This method will invoke the default action of packing all objects in an arbitrary order. + /// Packing results + public virtual PackBuilderResults Pack(PackBuilderOptions options) + { + return InternalPack(options, builder => + { + foreach (GitObject obj in repo.ObjectDatabase) + { + builder.Add(obj.Id); + } + }); + } + + /// + /// Packs objects in the chosen by the packDelegate action + /// and write a pack (.pack) and index (.idx) files for them + /// + /// Packing options + /// Packing action + /// Packing results + public virtual PackBuilderResults Pack(PackBuilderOptions options, Action packDelegate) + { + return InternalPack(options, packDelegate); + } + + /// + /// Perform a three-way merge of two commits, looking up their + /// commit ancestor. The returned index will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The first tree + /// The second tree + /// The controlling the merge + /// The containing the merged trees and any conflicts, or null if the merge stopped early due to conflicts. + /// The index must be disposed by the caller. + public virtual TransientIndex MergeCommitsIntoIndex(Commit ours, Commit theirs, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(ours, "ours"); + Ensure.ArgumentNotNull(theirs, "theirs"); + + options = options ?? new MergeTreeOptions(); + + bool earlyStop; + var indexHandle = MergeCommits(ours, theirs, options, out earlyStop); + if (earlyStop) + { + if (indexHandle != null) + { + indexHandle.Dispose(); + } + return null; + } + var result = new TransientIndex(indexHandle, repo); + return result; + } + + /// + /// Performs a cherry-pick of onto commit. + /// + /// The commit to cherry-pick. + /// The commit to cherry-pick onto. + /// Which commit to consider the parent for the diff when cherry-picking a merge commit. + /// The options for the merging in the cherry-pick operation. + /// The containing the cherry-pick result tree and any conflicts, or null if the merge stopped early due to conflicts. + /// The index must be disposed by the caller. + public virtual TransientIndex CherryPickCommitIntoIndex(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(cherryPickCommit, "cherryPickCommit"); + Ensure.ArgumentNotNull(cherryPickOnto, "cherryPickOnto"); + + options = options ?? new MergeTreeOptions(); + + bool earlyStop; + var indexHandle = CherryPickCommit(cherryPickCommit, cherryPickOnto, mainline, options, out earlyStop); + if (earlyStop) + { + if (indexHandle != null) + { + indexHandle.Dispose(); + } + return null; + } + var result = new TransientIndex(indexHandle, repo); + return result; + } + + /// + /// Perform a three-way merge of two commits, looking up their + /// commit ancestor. The returned index will contain the results + /// of the merge and can be examined for conflicts. + /// + /// The first tree + /// The second tree + /// The controlling the merge + /// True if the merge stopped early due to conflicts + /// The containing the merged trees and any conflicts + private IndexHandle MergeCommits(Commit ours, Commit theirs, MergeTreeOptions options, out bool earlyStop) + { + GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL; + if (options.SkipReuc) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + if (options.FindRenames) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES; + } + if (options.FailOnConflict) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + var mergeOptions = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = mergeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + }; + using (var oneHandle = Proxy.git_object_lookup(repo.Handle, ours.Id, GitObjectType.Commit)) + using (var twoHandle = Proxy.git_object_lookup(repo.Handle, theirs.Id, GitObjectType.Commit)) + { + var indexHandle = Proxy.git_merge_commits(repo.Handle, oneHandle, twoHandle, mergeOptions, out earlyStop); + return indexHandle; + } + } + + /// + /// Performs a cherry-pick of onto commit. + /// + /// The commit to cherry-pick. + /// The commit to cherry-pick onto. + /// Which commit to consider the parent for the diff when cherry-picking a merge commit. + /// The options for the merging in the cherry-pick operation. + /// True if the cherry-pick stopped early due to conflicts + /// The containing the cherry-pick result tree and any conflicts + private IndexHandle CherryPickCommit(Commit cherryPickCommit, Commit cherryPickOnto, int mainline, MergeTreeOptions options, out bool earlyStop) + { + GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL; + if (options.SkipReuc) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + if (options.FindRenames) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES; + } + if (options.FailOnConflict) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + var mergeOptions = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = mergeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + }; + + using (var cherryPickOntoHandle = Proxy.git_object_lookup(repo.Handle, cherryPickOnto.Id, GitObjectType.Commit)) + using (var cherryPickCommitHandle = Proxy.git_object_lookup(repo.Handle, cherryPickCommit.Id, GitObjectType.Commit)) + { + var indexHandle = Proxy.git_cherrypick_commit(repo.Handle, cherryPickCommitHandle, cherryPickOntoHandle, (uint)mainline, mergeOptions, out earlyStop); + return indexHandle; + } + } + + + /// + /// Packs objects in the and write a pack (.pack) and index (.idx) files for them. + /// For internal use only. + /// + /// Packing options + /// Packing action + /// Packing results + private PackBuilderResults InternalPack(PackBuilderOptions options, Action packDelegate) + { + Ensure.ArgumentNotNull(options, "options"); + Ensure.ArgumentNotNull(packDelegate, "packDelegate"); + + PackBuilderResults results = new PackBuilderResults(); + + using (PackBuilder builder = new PackBuilder(repo)) + { + // set pre-build options + builder.SetMaximumNumberOfThreads(options.MaximumNumberOfThreads); + + // call the provided action + packDelegate(builder); + + // writing the pack and index files + builder.Write(options.PackDirectoryPath); + + // adding the results to the PackBuilderResults object + results.WrittenObjectsCount = builder.WrittenObjectsCount; + } + + return results; + } + + /// + /// Performs a revert of onto commit. + /// + /// The commit to revert. + /// The commit to revert onto. + /// Which commit to consider the parent for the diff when reverting a merge commit. + /// The options for the merging in the revert operation. + /// A result containing a if the revert was successful and a list of s if it is not. + public virtual MergeTreeResult RevertCommit(Commit revertCommit, Commit revertOnto, int mainline, MergeTreeOptions options) + { + Ensure.ArgumentNotNull(revertCommit, "revertCommit"); + Ensure.ArgumentNotNull(revertOnto, "revertOnto"); + + options = options ?? new MergeTreeOptions(); + + // We throw away the index after looking at the conflicts, so we'll never need the REUC + // entries to be there + GitMergeFlag mergeFlags = GitMergeFlag.GIT_MERGE_NORMAL | GitMergeFlag.GIT_MERGE_SKIP_REUC; + if (options.FindRenames) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FIND_RENAMES; + } + if (options.FailOnConflict) + { + mergeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + + var opts = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = mergeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit + }; + + bool earlyStop; + + using (var revertOntoHandle = Proxy.git_object_lookup(repo.Handle, revertOnto.Id, GitObjectType.Commit)) + using (var revertCommitHandle = Proxy.git_object_lookup(repo.Handle, revertCommit.Id, GitObjectType.Commit)) + using (var indexHandle = Proxy.git_revert_commit(repo.Handle, revertCommitHandle, revertOntoHandle, (uint)mainline, opts, out earlyStop)) + { + MergeTreeResult revertTreeResult; + + // Stopped due to FailOnConflict so there's no index or conflict list + if (earlyStop) + { + return new MergeTreeResult(Array.Empty()); + } + + if (Proxy.git_index_has_conflicts(indexHandle)) + { + List conflicts = new List(); + Conflict conflict; + using (ConflictIteratorHandle iterator = Proxy.git_index_conflict_iterator_new(indexHandle)) + { + while ((conflict = Proxy.git_index_conflict_next(iterator)) != null) + { + conflicts.Add(conflict); + } + } + revertTreeResult = new MergeTreeResult(conflicts); + } + else + { + var treeId = Proxy.git_index_write_tree_to(indexHandle, repo.Handle); + revertTreeResult = new MergeTreeResult(this.repo.Lookup(treeId)); + } + + return revertTreeResult; + } } } } diff --git a/LibGit2Sharp/ObjectDatabaseExtensions.cs b/LibGit2Sharp/ObjectDatabaseExtensions.cs deleted file mode 100644 index 1ed70be6c..000000000 --- a/LibGit2Sharp/ObjectDatabaseExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.IO; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ObjectDatabaseExtensions - { - /// - /// Create a TAR archive of the given tree. - /// - /// The object database. - /// The tree. - /// The archive path. - public static void Archive(this ObjectDatabase odb, Tree tree, string archivePath) - { - using (var output = new FileStream(archivePath, FileMode.Create)) - using (var archiver = new TarArchiver(output)) - { - odb.Archive(tree, archiver); - } - } - - /// - /// Create a TAR archive of the given commit. - /// - /// The object database. - /// commit. - /// The archive path. - public static void Archive(this ObjectDatabase odb, Commit commit, string archivePath) - { - using (var output = new FileStream(archivePath, FileMode.Create)) - using (var archiver = new TarArchiver(output)) - { - odb.Archive(commit, archiver); - } - } - } -} diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 7b247e8a1..d87bbcb34 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -39,7 +39,7 @@ internal ObjectId(GitOid oid) { if (oid.Id == null || oid.Id.Length != rawSize) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "A non null array of {0} bytes is expected.", rawSize), "oid"); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "A non null array of {0} bytes is expected.", rawSize), nameof(oid)); } this.oid = oid; @@ -57,6 +57,32 @@ public ObjectId(byte[] rawId) Ensure.ArgumentConformsTo(rawId, b => b.Length == rawSize, "rawId"); } + internal static unsafe ObjectId BuildFromPtr(IntPtr ptr) + { + return BuildFromPtr((git_oid*)ptr.ToPointer()); + } + + internal static unsafe ObjectId BuildFromPtr(git_oid* id) + { + return id == null ? null : new ObjectId(id->Id); + } + + internal unsafe ObjectId(byte* rawId) + { + byte[] id = new byte[GitOid.Size]; + + fixed (byte* p = id) + { + for (int i = 0; i < rawSize; i++) + { + p[i] = rawId[i]; + } + } + + this.oid = new GitOid { Id = id }; + this.sha = ToString(oid.Id, oid.Id.Length * 2); + } + /// /// Initializes a new instance of the class. /// @@ -119,10 +145,10 @@ public static bool TryParse(string sha, out ObjectId result) } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as ObjectId); @@ -148,7 +174,7 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// /// The that represents the current . public override string ToString() @@ -157,7 +183,7 @@ public override string ToString() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// /// The number of chars the should be truncated to. /// The that represents the current . @@ -297,12 +323,14 @@ private static bool LooksValid(string objectId, bool throwIfInvalid) return false; } - throw new ArgumentException( - string.Format(CultureInfo.InvariantCulture, "'{0}' is not a valid object identifier. Its length should be {1}.", objectId, HexSize), - "objectId"); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "'{0}' is not a valid object identifier. Its length should be {1}.", + objectId, + HexSize), + nameof(objectId)); } - return objectId.All(c => hexDigits.Contains(c.ToString(CultureInfo.InvariantCulture))); + return objectId.All(c => hexDigits.IndexOf(c) >= 0); } /// diff --git a/LibGit2Sharp/OdbBackend.cs b/LibGit2Sharp/OdbBackend.cs index 149b996ce..645d0ac5f 100644 --- a/LibGit2Sharp/OdbBackend.cs +++ b/LibGit2Sharp/OdbBackend.cs @@ -33,10 +33,7 @@ internal void Free() /// /// In your subclass, override this member to provide the list of actions your backend supports. /// - protected abstract OdbBackendOperations SupportedOperations - { - get; - } + protected abstract OdbBackendOperations SupportedOperations { get; } /// /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in @@ -64,10 +61,9 @@ protected UnmanagedMemoryStream AllocateAndBuildFrom(byte[] bytes) /// A Stream for you to write to and then return. Do not dispose this object before returning it. protected unsafe UnmanagedMemoryStream Allocate(long size) { - if (size < 0 || - (UIntPtr.Size == sizeof(int) && size > int.MaxValue)) + if (size < 0 || (UIntPtr.Size == sizeof(int) && size > int.MaxValue)) { - throw new ArgumentOutOfRangeException("size"); + throw new ArgumentOutOfRangeException(nameof(size)); } IntPtr buffer = Proxy.git_odb_backend_malloc(this.GitOdbBackendPointer, new UIntPtr((ulong)size)); @@ -242,7 +238,7 @@ private static OdbBackend MarshalOdbBackend(IntPtr backendPtr) if (odbBackend == null) { - Proxy.giterr_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed OdbBackend."); + Proxy.git_error_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed OdbBackend."); return null; } @@ -292,7 +288,7 @@ private unsafe static int Read( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } finally @@ -330,7 +326,7 @@ private unsafe static int ReadPrefix( try { - var shortSha = ObjectId.ToString(short_oid.Id, (int) len); + var shortSha = ObjectId.ToString(short_oid.Id, (int)len); ObjectId oid; ObjectType objectType; @@ -356,7 +352,7 @@ private unsafe static int ReadPrefix( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } finally @@ -401,7 +397,7 @@ private static int ReadHeader( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } @@ -432,7 +428,7 @@ private static unsafe int Write( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } } @@ -440,13 +436,11 @@ private static unsafe int Write( private static int WriteStream( out IntPtr stream_out, IntPtr backend, - UIntPtr len, + long len, GitObjectType type) { stream_out = IntPtr.Zero; - long length = ConverToLong(len); - OdbBackend odbBackend = MarshalOdbBackend(backend); if (odbBackend == null) { @@ -458,7 +452,7 @@ private static int WriteStream( try { OdbBackendStream stream; - int toReturn = odbBackend.WriteStream(length, objectType, out stream); + int toReturn = odbBackend.WriteStream(len, objectType, out stream); if (toReturn == 0) { @@ -469,7 +463,7 @@ private static int WriteStream( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } } @@ -501,7 +495,7 @@ private static int ReadStream( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } } @@ -522,7 +516,7 @@ private static bool Exists( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return false; } } @@ -547,7 +541,7 @@ private static int ExistsPrefix( found_oid.Id = ObjectId.Zero.RawId; int result = odbBackend.ExistsPrefix(shortSha, out found); - if (result == (int) GitErrorCode.Ok) + if (result == (int)GitErrorCode.Ok) { found_oid.Id = found.RawId; } @@ -556,7 +550,7 @@ private static int ExistsPrefix( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } } @@ -578,7 +572,7 @@ private static int Foreach( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); return (int)GitErrorCode.Error; } } @@ -607,7 +601,7 @@ private static void Free( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } @@ -624,7 +618,7 @@ private unsafe int CallbackMethod(ObjectId id) { var oid = id.RawId; - fixed(void* ptr = &oid[0]) + fixed (void* ptr = &oid[0]) { return cb(new IntPtr(ptr), data); } @@ -641,11 +635,10 @@ internal static long ConverToLong(UIntPtr len) { if (len.ToUInt64() > long.MaxValue) { - throw new InvalidOperationException( - string.Format( - CultureInfo.InvariantCulture, - "Provided length ({0}) exceeds long.MaxValue ({1}).", - len.ToUInt64(), long.MaxValue)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Provided length ({0}) exceeds long.MaxValue ({1}).", + len.ToUInt64(), + long.MaxValue)); } return (long)len.ToUInt64(); diff --git a/LibGit2Sharp/OdbBackendStream.cs b/LibGit2Sharp/OdbBackendStream.cs index 6397b7bb3..e7d177903 100644 --- a/LibGit2Sharp/OdbBackendStream.cs +++ b/LibGit2Sharp/OdbBackendStream.cs @@ -45,48 +45,34 @@ protected virtual void Dispose() /// /// If true, then it is legal to call the Read method. /// - public abstract bool CanRead - { - get; - } + public abstract bool CanRead { get; } /// /// If true, then it is legal to call the Write and FinalizeWrite methods. /// - public abstract bool CanWrite - { - get; - } + public abstract bool CanWrite { get; } /// /// Requests that the stream write the next length bytes of the stream to the provided Stream object. /// - public abstract int Read( - Stream dataStream, - long length); + public abstract int Read(Stream dataStream, long length); /// /// Requests that the stream write the first length bytes of the provided Stream object to the stream. /// - public abstract int Write( - Stream dataStream, - long length); + public abstract int Write(Stream dataStream, long length); /// /// After all bytes have been written to the stream, the object ID is provided to FinalizeWrite. /// - public abstract int FinalizeWrite( - ObjectId id); + public abstract int FinalizeWrite(ObjectId id); /// /// The backend object this stream was created by. /// public virtual OdbBackend Backend { - get - { - return this.backend; - } + get { return this.backend; } } private readonly OdbBackend backend; @@ -138,10 +124,7 @@ private static class BackendStreamEntryPoints public static readonly GitOdbBackendStream.finalize_write_callback FinalizeWriteCallback = FinalizeWrite; public static readonly GitOdbBackendStream.free_callback FreeCallback = Free; - private unsafe static int Read( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private unsafe static int Read(IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -157,7 +140,7 @@ private unsafe static int Read( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } } @@ -165,10 +148,7 @@ private unsafe static int Read( return (int)GitErrorCode.Error; } - private static unsafe int Write( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private static unsafe int Write(IntPtr stream, IntPtr buffer, UIntPtr len) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -184,7 +164,7 @@ private static unsafe int Write( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } } @@ -192,9 +172,7 @@ private static unsafe int Write( return (int)GitErrorCode.Error; } - private static int FinalizeWrite( - IntPtr stream, - ref GitOid oid) + private static int FinalizeWrite(IntPtr stream, ref GitOid oid) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -206,15 +184,14 @@ private static int FinalizeWrite( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } return (int)GitErrorCode.Error; } - private static void Free( - IntPtr stream) + private static void Free(IntPtr stream) { OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; @@ -226,7 +203,7 @@ private static void Free( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } } diff --git a/LibGit2Sharp/PackBuilder.cs b/LibGit2Sharp/PackBuilder.cs new file mode 100644 index 000000000..2ede4ab7b --- /dev/null +++ b/LibGit2Sharp/PackBuilder.cs @@ -0,0 +1,203 @@ +using System; +using System.IO; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Representation of a git PackBuilder. + /// + public sealed class PackBuilder : IDisposable + { + private readonly PackBuilderHandle packBuilderHandle; + + /// + /// Constructs a PackBuilder for a . + /// + internal PackBuilder(Repository repository) + { + Ensure.ArgumentNotNull(repository, "repository"); + + packBuilderHandle = Proxy.git_packbuilder_new(repository.Handle); + } + + /// + /// Inserts a single to the PackBuilder. + /// For an optimal pack it's mandatory to insert objects in recency order, commits followed by trees and blobs. (quoted from libgit2 API ref) + /// + /// The object to be inserted. + /// if the gitObject is null + public void Add(T gitObject) where T : GitObject + { + Ensure.ArgumentNotNull(gitObject, "gitObject"); + + Add(gitObject.Id); + } + + /// + /// Recursively inserts a and its referenced objects. + /// Inserts the object as well as any object it references. + /// + /// The object to be inserted recursively. + /// if the gitObject is null + public void AddRecursively(T gitObject) where T : GitObject + { + Ensure.ArgumentNotNull(gitObject, "gitObject"); + + AddRecursively(gitObject.Id); + } + + /// + /// Inserts a single object to the PackBuilder by its . + /// For an optimal pack it's mandatory to insert objects in recency order, commits followed by trees and blobs. (quoted from libgit2 API ref) + /// + /// The object ID to be inserted. + /// if the id is null + public void Add(ObjectId id) + { + Ensure.ArgumentNotNull(id, "id"); + + Proxy.git_packbuilder_insert(packBuilderHandle, id, null); + } + + /// + /// Recursively inserts an object and its referenced objects by its . + /// Inserts the object as well as any object it references. + /// + /// The object ID to be recursively inserted. + /// if the id is null + public void AddRecursively(ObjectId id) + { + Ensure.ArgumentNotNull(id, "id"); + + Proxy.git_packbuilder_insert_recur(packBuilderHandle, id, null); + } + + /// + /// Disposes the PackBuilder object. + /// + void IDisposable.Dispose() + { + packBuilderHandle.SafeDispose(); + } + + /// + /// Writes the pack file and corresponding index file to path. + /// + /// The path that pack and index files will be written to it. + internal void Write(string path) + { + Proxy.git_packbuilder_write(packBuilderHandle, path); + } + + /// + /// Sets number of threads to spawn. + /// + /// Returns the number of actual threads to be used. + /// The Number of threads to spawn. An argument of 0 ensures using all available CPUs + internal int SetMaximumNumberOfThreads(int nThread) + { + // Libgit2 set the number of threads to 1 by default, 0 ensures git_online_cpus + return (int)Proxy.git_packbuilder_set_threads(packBuilderHandle, (uint)nThread); + } + + /// + /// Number of objects the PackBuilder will write out. + /// + internal long ObjectsCount + { + get { return (long)Proxy.git_packbuilder_object_count(packBuilderHandle); } + } + + /// + /// Number of objects the PackBuilder has already written out. + /// This is only correct after the pack file has been written. + /// + internal long WrittenObjectsCount + { + get { return (long)Proxy.git_packbuilder_written(packBuilderHandle); } + } + + internal PackBuilderHandle Handle + { + get { return packBuilderHandle; } + } + } + + /// + /// The results of pack process of the . + /// + public struct PackBuilderResults + { + /// + /// Number of objects the PackBuilder has already written out. + /// + public long WrittenObjectsCount { get; internal set; } + } + + /// + /// Packing options of the . + /// + public sealed class PackBuilderOptions + { + private string path; + private int nThreads; + + /// + /// Constructor + /// + /// Directory path to write the pack and index files to it + /// The default value for maximum number of threads to spawn is 0 which ensures using all available CPUs. + /// if packDirectory is null or empty + /// if packDirectory doesn't exist + public PackBuilderOptions(string packDirectory) + { + PackDirectoryPath = packDirectory; + MaximumNumberOfThreads = 0; + } + + /// + /// Directory path to write the pack and index files to it. + /// + public string PackDirectoryPath + { + set + { + Ensure.ArgumentNotNullOrEmptyString(value, "packDirectory"); + + if (!Directory.Exists(value)) + { + throw new DirectoryNotFoundException("The Directory " + value + " does not exist."); + } + + path = value; + } + get + { + return path; + } + } + + /// + /// Maximum number of threads to spawn. + /// The default value is 0 which ensures using all available CPUs. + /// + public int MaximumNumberOfThreads + { + set + { + if (value < 0) + { + throw new ArgumentException("Argument can not be negative", nameof(value)); + } + + nThreads = value; + } + get + { + return nThreads; + } + } + } +} diff --git a/LibGit2Sharp/Patch.cs b/LibGit2Sharp/Patch.cs index 4bbb884bd..50157eb32 100644 --- a/LibGit2Sharp/Patch.cs +++ b/LibGit2Sharp/Patch.cs @@ -16,7 +16,7 @@ namespace LibGit2Sharp /// deleted, modified, ..., then consider using a simpler . /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Patch : IEnumerable + public class Patch : IEnumerable, IDiffResult { private readonly StringBuilder fullPatchBuilder = new StringBuilder(); @@ -30,35 +30,39 @@ public class Patch : IEnumerable protected Patch() { } - internal Patch(DiffSafeHandle diff) + internal unsafe Patch(DiffHandle diff) { - int count = Proxy.git_diff_num_deltas(diff); - for (int i = 0; i < count; i++) + using (diff) { - using (var patch = Proxy.git_patch_from_diff(diff, i)) + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) { - var delta = Proxy.git_diff_get_delta(diff, i); - AddFileChange(delta); - Proxy.git_patch_print(patch, PrintCallBack); + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + AddFileChange(delta); + Proxy.git_patch_print(patch, PrintCallBack); + } } - } } - private void AddFileChange(GitDiffDelta delta) + private unsafe void AddFileChange(git_diff_delta* delta) { var treeEntryChanges = new TreeEntryChanges(delta); - changes.Add(treeEntryChanges.Path, new PatchEntryChanges(delta.IsBinary(), treeEntryChanges)); + changes.Add(treeEntryChanges.Path, new PatchEntryChanges(delta->flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY), treeEntryChanges)); } - private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) + private unsafe int PrintCallBack(git_diff_delta* delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { string patchPart = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); // Deleted files mean no "new file" path - var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; + var pathPtr = delta->new_file.Path != null + ? delta->new_file.Path + : delta->old_file.Path; var filePath = LaxFilePathMarshaler.FromNative(pathPtr); PatchEntryChanges currentChange = this[filePath]; @@ -73,12 +77,14 @@ private int PrintCallBack(GitDiffDelta delta, GitDiffHunk hunk, GitDiffLine line case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: linesAdded++; currentChange.LinesAdded++; + currentChange.AddedLines.Add(new Line(line.NewLineNo, patchPart)); prefix = "+"; break; case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION: linesDeleted++; currentChange.LinesDeleted++; + currentChange.DeletedLines.Add(new Line(line.OldLineNo, patchPart)); prefix = "-"; break; } @@ -174,8 +180,29 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "+{0} -{1}", linesAdded, linesDeleted); + "+{0} -{1}", + linesAdded, + linesDeleted); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // This doesn't do anything yet because it loads everything + // eagerly and disposes of the diff handle in the constructor. + } } } diff --git a/LibGit2Sharp/PatchStats.cs b/LibGit2Sharp/PatchStats.cs index f22eecb1a..3d6bb46cd 100644 --- a/LibGit2Sharp/PatchStats.cs +++ b/LibGit2Sharp/PatchStats.cs @@ -13,7 +13,7 @@ namespace LibGit2Sharp /// The individual patches for each file can be accessed through the indexer of this class. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class PatchStats : IEnumerable + public class PatchStats : IEnumerable, IDiffResult { private readonly IDictionary changes = new Dictionary(); private readonly int totalLinesAdded; @@ -25,25 +25,27 @@ public class PatchStats : IEnumerable protected PatchStats() { } - internal PatchStats(DiffSafeHandle diff) + internal unsafe PatchStats(DiffHandle diff) { - int count = Proxy.git_diff_num_deltas(diff); - for (int i = 0; i < count; i++) + using (diff) { - using (var patch = Proxy.git_patch_from_diff(diff, i)) + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) { - var delta = Proxy.git_diff_get_delta(diff, i); - var pathPtr = delta.NewFile.Path != IntPtr.Zero ? delta.NewFile.Path : delta.OldFile.Path; - var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); + using (var patch = Proxy.git_patch_from_diff(diff, i)) + { + var delta = Proxy.git_diff_get_delta(diff, i); + var pathPtr = delta->new_file.Path != null ? delta->new_file.Path : delta->old_file.Path; + var newFilePath = LaxFilePathMarshaler.FromNative(pathPtr); - var stats = Proxy.git_patch_line_stats(patch); - int added = stats.Item1; - int deleted = stats.Item2; - changes.Add(newFilePath, new ContentChangeStats(added, deleted)); - totalLinesAdded += added; - totalLinesDeleted += deleted; + var stats = Proxy.git_patch_line_stats(patch); + int added = stats.Item1; + int deleted = stats.Item2; + changes.Add(newFilePath, new ContentChangeStats(added, deleted)); + totalLinesAdded += added; + totalLinesDeleted += deleted; + } } - } } @@ -75,7 +77,7 @@ IEnumerator IEnumerable.GetEnumerator() /// public virtual ContentChangeStats this[string path] { - get { return this[(FilePath) path]; } + get { return this[(FilePath)path]; } } private ContentChangeStats this[FilePath path] @@ -111,9 +113,30 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, "+{0} -{1}", - TotalLinesAdded, TotalLinesDeleted); + return string.Format(CultureInfo.InvariantCulture, + "+{0} -{1}", + TotalLinesAdded, + TotalLinesDeleted); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + // This doesn't do anything yet because it loads everything + // eagerly and disposes of the diff handle in the constructor. + } } } diff --git a/LibGit2Sharp/PeelException.cs b/LibGit2Sharp/PeelException.cs new file mode 100644 index 000000000..b5a3d628b --- /dev/null +++ b/LibGit2Sharp/PeelException.cs @@ -0,0 +1,73 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when a tag cannot be peeled to the + /// target type due to the object model. + /// +#if NETFRAMEWORK + [Serializable] +#endif + public class PeelException : NativeException + { + /// + /// Initializes a new instance of the class. + /// + public PeelException() + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A message that describes the error. + public PeelException(string message) + : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public PeelException(string format, params object[] args) + : base(format, args) + { } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. + public PeelException(string message, Exception innerException) + : base(message, innerException) + { } + +#if NETFRAMEWORK + /// + /// Initializes a new instance of the class with a serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected PeelException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal PeelException(string message, GitErrorCategory category) + : base(message, category) + { } + + internal override GitErrorCode ErrorCode + { + get + { + return GitErrorCode.Peel; + } + } + } +} diff --git a/LibGit2Sharp/Properties/AssemblyInfo.cs b/LibGit2Sharp/Properties/AssemblyInfo.cs deleted file mode 100644 index 4e510cf50..000000000 --- a/LibGit2Sharp/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. - -[assembly: AssemblyTitle("LibGit2Sharp")] -[assembly: AssemblyDescription("LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono.")] -[assembly: AssemblyCompany("LibGit2Sharp contributors")] - -#if DEBUG -[assembly: AssemblyConfiguration("Debug")] -#else -[assembly: AssemblyConfiguration("Release")] -#endif - -[assembly: AssemblyProduct("LibGit2Sharp")] -[assembly: AssemblyCopyright("Copyright © 2011-2014 LibGit2Sharp contributors")] - -[assembly: CLSCompliant(true)] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. - -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM - -[assembly: Guid("c6f71967-5be1-49f5-b48e-861bff498ea3")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] - -[assembly: AssemblyVersion("0.19.0")] -[assembly: AssemblyFileVersion("0.19.0")] diff --git a/LibGit2Sharp/ProxyOptions.cs b/LibGit2Sharp/ProxyOptions.cs new file mode 100644 index 000000000..076c4e357 --- /dev/null +++ b/LibGit2Sharp/ProxyOptions.cs @@ -0,0 +1,119 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Options for connecting through a proxy. + /// + public sealed class ProxyOptions + { + /// + /// The type of proxy to use. Set to Auto by default. + /// + public ProxyType ProxyType { get; set; } = ProxyType.Auto; + + /// + /// The URL of the proxy when is set to Specified. + /// + public string Url { get; set; } + + /// + /// Handler to generate for authentication. + /// + public CredentialsHandler CredentialsProvider { get; set; } + + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to proceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + + internal unsafe GitProxyOptions CreateGitProxyOptions() + { + var gitProxyOptions = new GitProxyOptions + { + Version = 1, + Type = (GitProxyType)ProxyType + }; + + if (Url is not null) + { + gitProxyOptions.Url = StrictUtf8Marshaler.FromManaged(Url); + } + + if (CredentialsProvider is not null) + { + gitProxyOptions.Credentials = GitCredentialHandler; + } + + if (CertificateCheck is not null) + { + gitProxyOptions.CertificateCheck = GitCertificateCheck; + } + + return gitProxyOptions; + } + + private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFromUrl, GitCredentialType credTypes, IntPtr payload) + { + string url = LaxUtf8Marshaler.FromNative(cUrl); + string username = LaxUtf8Marshaler.FromNative(usernameFromUrl); + + SupportedCredentialTypes types = default(SupportedCredentialTypes); + if (credTypes.HasFlag(GitCredentialType.UserPassPlaintext)) + { + types |= SupportedCredentialTypes.UsernamePassword; + } + if (credTypes.HasFlag(GitCredentialType.Default)) + { + types |= SupportedCredentialTypes.Default; + } + + ptr = IntPtr.Zero; + try + { + var cred = CredentialsProvider(url, username, types); + if (cred == null) + { + return (int)GitErrorCode.PassThrough; + } + return cred.GitCredentialHandler(out ptr); + } + catch (Exception exception) + { + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + return (int)GitErrorCode.Error; + } + } + + private unsafe int GitCertificateCheck(git_certificate* certPtr, int valid, IntPtr cHostname, IntPtr payload) + { + string hostname = LaxUtf8Marshaler.FromNative(cHostname); + Certificate cert = null; + + switch (certPtr->type) + { + case GitCertificateType.X509: + cert = new CertificateX509((git_certificate_x509*)certPtr); + break; + case GitCertificateType.Hostkey: + cert = new CertificateSsh((git_certificate_ssh*)certPtr); + break; + } + + bool result = false; + try + { + result = CertificateCheck(cert, valid != 0, hostname); + } + catch (Exception exception) + { + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + } + + return Proxy.ConvertResultToCancelFlag(result); + } + } +} diff --git a/LibGit2Sharp/ProxyType.cs b/LibGit2Sharp/ProxyType.cs new file mode 100644 index 000000000..13ec705ee --- /dev/null +++ b/LibGit2Sharp/ProxyType.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// The type of proxy to use. + /// + public enum ProxyType + { + /// + /// Do not attempt to connect through a proxy. + /// + None, + + /// + /// Try to auto-detect the proxy from the git configuration. + /// + Auto, + + /// + /// Connect via the URL given in the options. + /// + Specified + } +} diff --git a/LibGit2Sharp/PullOptions.cs b/LibGit2Sharp/PullOptions.cs index 914e6cae0..764715bb5 100644 --- a/LibGit2Sharp/PullOptions.cs +++ b/LibGit2Sharp/PullOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Parameters controlling Pull behavior. diff --git a/LibGit2Sharp/PushOptions.cs b/LibGit2Sharp/PushOptions.cs index 75b1b94ba..829eb0d60 100644 --- a/LibGit2Sharp/PushOptions.cs +++ b/LibGit2Sharp/PushOptions.cs @@ -5,13 +5,19 @@ namespace LibGit2Sharp /// /// Collection of parameters controlling Push behavior. /// - public sealed class PushOptions : ICredentialsProvider + public sealed class PushOptions { /// /// Handler to generate for authentication. /// public CredentialsHandler CredentialsProvider { get; set; } + /// + /// This handler will be called to let the user make a decision on whether to allow + /// the connection to preoceed based on the certificate presented by the server. + /// + public CertificateCheckHandler CertificateCheck { get; set; } + /// /// If the transport being used to push to the remote requires the creation /// of a pack file, this controls the number of worker threads used by @@ -39,5 +45,36 @@ public sealed class PushOptions : ICredentialsProvider /// be more than once every 0.5 seconds (in general). /// public PackBuilderProgressHandler OnPackBuilderProgress { get; set; } + + /// + /// Called once between the negotiation step and the upload. It provides + /// information about what updates will be performed. + /// + public PrePushHandler OnNegotiationCompletedBeforePush { get; set; } + + /// + /// Get/Set the custom headers. + /// + /// This allows you to set custom headers (e.g. X-Forwarded-For, + /// X-Request-Id, etc), + /// + /// + /// + /// Libgit2 sets some headers for HTTP requests (User-Agent, Host, + /// Accept, Content-Type, Transfer-Encoding, Content-Length, Accept) that + /// cannot be overriden. + /// + /// + /// var pushOptions - new PushOptions() { + /// CustomHeaders = new String[] {"X-Request-Id: 12345"} + /// }; + /// + /// The custom headers string array + public string[] CustomHeaders { get; set; } + + /// + /// Options for connecting through a proxy. + /// + public ProxyOptions ProxyOptions { get; set; } = new(); } } diff --git a/LibGit2Sharp/PushResult.cs b/LibGit2Sharp/PushResult.cs index bf85e3ab8..713f13a55 100644 --- a/LibGit2Sharp/PushResult.cs +++ b/LibGit2Sharp/PushResult.cs @@ -18,10 +18,7 @@ protected PushResult() /// public virtual IEnumerable FailedPushUpdates { - get - { - return failedPushUpdates; - } + get { return failedPushUpdates; } } /// @@ -30,10 +27,7 @@ public virtual IEnumerable FailedPushUpdates /// public virtual bool HasErrors { - get - { - return failedPushUpdates.Count > 0; - } + get { return failedPushUpdates.Count > 0; } } internal PushResult(List failedPushUpdates) diff --git a/LibGit2Sharp/PushUpdate.cs b/LibGit2Sharp/PushUpdate.cs new file mode 100644 index 000000000..0aa915dc0 --- /dev/null +++ b/LibGit2Sharp/PushUpdate.cs @@ -0,0 +1,54 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Represents an update which will be performed on the remote during push + /// + public class PushUpdate + { + internal PushUpdate(string srcRefName, ObjectId srcOid, string dstRefName, ObjectId dstOid) + { + DestinationObjectId = dstOid; + DestinationRefName = dstRefName; + SourceObjectId = srcOid; + SourceRefName = srcRefName; + } + + internal unsafe PushUpdate(git_push_update* update) + { + DestinationObjectId = ObjectId.BuildFromPtr(&update->dst); + DestinationRefName = LaxUtf8Marshaler.FromNative(update->dst_refname); + SourceObjectId = ObjectId.BuildFromPtr(&update->src); + SourceRefName = LaxUtf8Marshaler.FromNative(update->src_refname); + } + /// + /// Empty constructor to support test suites + /// + protected PushUpdate() + { + DestinationObjectId = ObjectId.Zero; + DestinationRefName = string.Empty; + SourceObjectId = ObjectId.Zero; + SourceRefName = string.Empty; + } + + /// + /// The source name of the reference + /// + public readonly string SourceRefName; + /// + /// The name of the reference to update on the server + /// + public readonly string DestinationRefName; + /// + /// The current target of the reference + /// + public readonly ObjectId SourceObjectId; + /// + /// The new target for the reference + /// + public readonly ObjectId DestinationObjectId; + } +} diff --git a/LibGit2Sharp/Rebase.cs b/LibGit2Sharp/Rebase.cs new file mode 100644 index 000000000..c573ffa65 --- /dev/null +++ b/LibGit2Sharp/Rebase.cs @@ -0,0 +1,317 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System.Globalization; + +namespace LibGit2Sharp +{ + /// + /// The type of operation to be performed in a rebase step. + /// + public enum RebaseStepOperation + { + /// + /// Commit is to be cherry-picked. + /// + Pick = 0, + + /// + /// Cherry-pick the commit and edit the commit message. + /// + Reword, + + /// + /// Cherry-pick the commit but allow user to edit changes. + /// + Edit, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be merged with the previous message. + /// + Squash, + + /// + /// Commit is to be squashed into previous commit. The commit + /// message will be discarded. + /// + Fixup, + + // + // No commit to cherry-pick. Run the given command and continue + // if successful. + // + // Exec + } + + /// + /// Encapsulates a rebase operation. + /// + public class Rebase + { + internal readonly Repository repository; + + /// + /// Needed for mocking purposes. + /// + protected Rebase() + { } + + internal Rebase(Repository repo) + { + this.repository = repo; + } + + unsafe AnnotatedCommitHandle AnnotatedCommitHandleFromRefHandle(ReferenceHandle refHandle) + { + return (refHandle == null) ? + new AnnotatedCommitHandle(null, false) : + Proxy.git_annotated_commit_from_ref(this.repository.Handle, refHandle); + } + + /// + /// Start a rebase operation. + /// + /// The branch to rebase. + /// The starting commit to rebase. + /// The branch to rebase onto. + /// The of who added the change to the repository. + /// The that specify the rebase behavior. + /// true if completed successfully, false if conflicts encountered. + public virtual RebaseResult Start(Branch branch, Branch upstream, Branch onto, Identity committer, RebaseOptions options) + { + Ensure.ArgumentNotNull(upstream, "upstream"); + + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(); + + if (this.repository.Info.CurrentOperation != CurrentOperation.None) + { + throw new LibGit2SharpException("A {0} operation is already in progress.", + this.repository.Info.CurrentOperation); + } + + Func RefHandleFromBranch = (Branch b) => + { + return (b == null) ? + null : + this.repository.Refs.RetrieveReferencePtr(b.CanonicalName); + }; + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (ReferenceHandle branchRefPtr = RefHandleFromBranch(branch)) + using (ReferenceHandle upstreamRefPtr = RefHandleFromBranch(upstream)) + using (ReferenceHandle ontoRefPtr = RefHandleFromBranch(onto)) + using (AnnotatedCommitHandle annotatedBranchCommitHandle = AnnotatedCommitHandleFromRefHandle(branchRefPtr)) + using (AnnotatedCommitHandle upstreamRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(upstreamRefPtr)) + using (AnnotatedCommitHandle ontoRefAnnotatedCommitHandle = AnnotatedCommitHandleFromRefHandle(ontoRefPtr)) + using (RebaseHandle rebaseOperationHandle = Proxy.git_rebase_init(this.repository.Handle, + annotatedBranchCommitHandle, + upstreamRefAnnotatedCommitHandle, + ontoRefAnnotatedCommitHandle, + gitRebaseOptions)) + { + RebaseResult rebaseResult = RebaseOperationImpl.Run(rebaseOperationHandle, + this.repository, + committer, + options); + return rebaseResult; + } + } + } + + /// + /// Continue the current rebase. + /// + /// The of who added the change to the repository. + /// The that specify the rebase behavior. + public virtual unsafe RebaseResult Continue(Identity committer, RebaseOptions options) + { + Ensure.ArgumentNotNull(committer, "committer"); + + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(); + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + // TODO: Should we check the pre-conditions for committing here + // for instance - what if we had failed on the git_rebase_finish call, + // do we want continue to be able to restart afterwords... + var rebaseCommitResult = Proxy.git_rebase_commit(rebase, null, committer); + + // Report that we just completed the step + if (options.RebaseStepCompleted != null) + { + // Get information on the current step + long currentStepIndex = Proxy.git_rebase_operation_current(rebase); + long totalStepCount = Proxy.git_rebase_operation_entrycount(rebase); + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebase, currentStepIndex); + + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8NoCleanupMarshaler.FromNative(gitRebasestepInfo->exec)); + + if (rebaseCommitResult.WasPatchAlreadyApplied) + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, currentStepIndex, totalStepCount)); + } + else + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepInfo, + repository.Lookup(new ObjectId(rebaseCommitResult.CommitId)), + currentStepIndex, + totalStepCount)); + } + } + + RebaseResult rebaseResult = RebaseOperationImpl.Run(rebase, repository, committer, options); + return rebaseResult; + } + } + } + + /// + /// Abort the rebase operation. + /// + public virtual void Abort() + { + Abort(null); + } + + /// + /// Abort the rebase operation. + /// + /// The that specify the rebase behavior. + public virtual void Abort(RebaseOptions options) + { + options = options ?? new RebaseOptions(); + + EnsureNonBareRepo(); + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + checkout_options = checkoutOptionsWrapper.Options, + }; + + using (RebaseHandle rebase = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + Proxy.git_rebase_abort(rebase); + } + } + } + + /// + /// The info on the current step. + /// + public virtual unsafe RebaseStepInfo GetCurrentStepInfo() + { + if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) + { + return null; + } + + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + long currentStepIndex = Proxy.git_rebase_operation_current(rebaseHandle); + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, currentStepIndex); + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); + return stepInfo; + } + } + + /// + /// Get info on the specified step + /// + /// + /// + public virtual unsafe RebaseStepInfo GetStepInfo(long stepIndex) + { + if (repository.Info.CurrentOperation != LibGit2Sharp.CurrentOperation.RebaseMerge) + { + return null; + } + + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + git_rebase_operation* gitRebasestepInfo = Proxy.git_rebase_operation_byindex(rebaseHandle, stepIndex); + var stepInfo = new RebaseStepInfo(gitRebasestepInfo->type, + repository.Lookup(ObjectId.BuildFromPtr(&gitRebasestepInfo->id)), + LaxUtf8Marshaler.FromNative(gitRebasestepInfo->exec)); + return stepInfo; + } + } + + /// + /// + /// + /// + public virtual long GetCurrentStepIndex() + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + return Proxy.git_rebase_operation_current(rebaseHandle); + } + } + + /// + /// + /// + /// + public virtual long GetTotalStepCount() + { + GitRebaseOptions gitRebaseOptions = new GitRebaseOptions() + { + version = 1, + }; + + using (RebaseHandle rebaseHandle = Proxy.git_rebase_open(repository.Handle, gitRebaseOptions)) + { + return Proxy.git_rebase_operation_entrycount(rebaseHandle); + } + } + + private void EnsureNonBareRepo() + { + if (this.repository.Info.IsBare) + { + throw new BareRepositoryException("Rebase operations in a bare repository are not supported."); + } + } + } +} diff --git a/LibGit2Sharp/RebaseOperationImpl.cs b/LibGit2Sharp/RebaseOperationImpl.cs new file mode 100644 index 000000000..c35564573 --- /dev/null +++ b/LibGit2Sharp/RebaseOperationImpl.cs @@ -0,0 +1,279 @@ +using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System.Globalization; + +namespace LibGit2Sharp +{ + internal class RebaseOperationImpl + { + /// + /// Run a rebase to completion, a conflict, or a requested stop point. + /// + /// Handle to the rebase operation. + /// Repository in which rebase operation is being run. + /// Committer Identity to use for the rebased commits. + /// Options controlling rebase behavior. + /// RebaseResult that describes the result of the rebase operation. + public static RebaseResult Run(RebaseHandle rebaseOperationHandle, + Repository repository, + Identity committer, + RebaseOptions options) + { + Ensure.ArgumentNotNull(rebaseOperationHandle, "rebaseOperationHandle"); + Ensure.ArgumentNotNull(repository, "repository"); + Ensure.ArgumentNotNull(committer, "committer"); + Ensure.ArgumentNotNull(options, "options"); + + RebaseResult rebaseResult = null; + + // This loop will run until a rebase result has been set. + while (rebaseResult == null) + { + RebaseProgress rebaseStepContext = NextRebaseStep(repository, rebaseOperationHandle); + + if (rebaseStepContext.current != -1) + { + rebaseResult = RunRebaseStep(rebaseOperationHandle, + repository, + committer, + options, + rebaseStepContext.current, + rebaseStepContext.total); + } + else + { + // No step to apply - need to complete the rebase. + rebaseResult = CompleteRebase(rebaseOperationHandle, committer); + } + } + + return rebaseResult; + } + + private static RebaseResult CompleteRebase(RebaseHandle rebaseOperationHandle, Identity committer) + { + long totalStepCount = Proxy.git_rebase_operation_entrycount(rebaseOperationHandle); + + // Rebase is completed! + Proxy.git_rebase_finish(rebaseOperationHandle, committer); + var rebaseResult = new RebaseResult(RebaseStatus.Complete, + totalStepCount, + totalStepCount, + null); + return rebaseResult; + } + + /// + /// Run the current rebase step. This will handle reporting that we are about to run a rebase step, + /// identifying and running the operation for the current step, and reporting the current step is completed. + /// + /// + /// + /// + /// + /// + /// + /// + private static unsafe RebaseResult RunRebaseStep(RebaseHandle rebaseOperationHandle, + Repository repository, + Identity committer, + RebaseOptions options, + long stepToApplyIndex, + long totalStepCount) + { + RebaseStepResult rebaseStepResult = null; + RebaseResult rebaseSequenceResult = null; + + git_rebase_operation* rebaseOp = Proxy.git_rebase_operation_byindex(rebaseOperationHandle, stepToApplyIndex); + ObjectId idOfCommitBeingRebased = ObjectId.BuildFromPtr(&rebaseOp->id); + + RebaseStepInfo stepToApplyInfo = new RebaseStepInfo(rebaseOp->type, + repository.Lookup(idOfCommitBeingRebased), + LaxUtf8NoCleanupMarshaler.FromNative(rebaseOp->exec)); + + // Report the rebase step we are about to perform. + if (options.RebaseStepStarting != null) + { + options.RebaseStepStarting(new BeforeRebaseStepInfo(stepToApplyInfo, stepToApplyIndex, totalStepCount)); + } + + // Perform the rebase step + git_rebase_operation* rebaseOpReport = Proxy.git_rebase_next(rebaseOperationHandle); + + // Verify that the information from the native library is consistent. + VerifyRebaseOp(rebaseOpReport, stepToApplyInfo); + + // Handle the result + switch (stepToApplyInfo.Type) + { + case RebaseStepOperation.Pick: + rebaseStepResult = ApplyPickStep(rebaseOperationHandle, repository, committer, options, stepToApplyInfo); + break; + case RebaseStepOperation.Squash: + case RebaseStepOperation.Edit: + // case RebaseStepOperation.Exec: + case RebaseStepOperation.Fixup: + case RebaseStepOperation.Reword: + // These operations are not yet supported by lg2. + throw new LibGit2SharpException("Rebase Operation Type ({0}) is not currently supported in LibGit2Sharp.", + stepToApplyInfo.Type); + default: + throw new ArgumentException(string.Format( + "Unexpected Rebase Operation Type: {0}", stepToApplyInfo.Type)); + } + + // Report that we just completed the step + if (options.RebaseStepCompleted != null && + (rebaseStepResult.Status == RebaseStepStatus.Committed || + rebaseStepResult.Status == RebaseStepStatus.ChangesAlreadyApplied)) + { + if (rebaseStepResult.ChangesAlreadyApplied) + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepToApplyInfo, stepToApplyIndex, totalStepCount)); + } + else + { + options.RebaseStepCompleted(new AfterRebaseStepInfo(stepToApplyInfo, + repository.Lookup(new ObjectId(rebaseStepResult.CommitId)), + stepToApplyIndex, + totalStepCount)); + } + } + + // If the result of the rebase step is something that requires us to stop + // running the rebase sequence operations, then report the result. + if (rebaseStepResult.Status == RebaseStepStatus.Conflicts) + { + rebaseSequenceResult = new RebaseResult(RebaseStatus.Conflicts, + stepToApplyIndex, + totalStepCount, + null); + } + + return rebaseSequenceResult; + } + + private static RebaseStepResult ApplyPickStep(RebaseHandle rebaseOperationHandle, Repository repository, Identity committer, RebaseOptions options, RebaseStepInfo stepToApplyInfo) + { + RebaseStepResult rebaseStepResult; + + // commit and continue. + if (repository.Index.IsFullyMerged) + { + Proxy.GitRebaseCommitResult rebase_commit_result = Proxy.git_rebase_commit(rebaseOperationHandle, null, committer); + + if (rebase_commit_result.WasPatchAlreadyApplied) + { + rebaseStepResult = new RebaseStepResult(RebaseStepStatus.ChangesAlreadyApplied); + } + else + { + rebaseStepResult = new RebaseStepResult(RebaseStepStatus.Committed, rebase_commit_result.CommitId); + } + } + else + { + rebaseStepResult = new RebaseStepResult(RebaseStepStatus.Conflicts); + } + + return rebaseStepResult; + } + + /// + /// Verify that the information in a GitRebaseOperation and a RebaseStepInfo agree + /// + /// + /// + private static unsafe void VerifyRebaseOp(git_rebase_operation* rebaseOpReport, RebaseStepInfo stepInfo) + { + // The step reported via querying by index and the step returned from git_rebase_next + // should be the same + if (rebaseOpReport == null || + ObjectId.BuildFromPtr(&rebaseOpReport->id) != stepInfo.Commit.Id || + rebaseOpReport->type != stepInfo.Type) + { + // This is indicative of a program error - should never happen. + throw new LibGit2SharpException("Unexpected step info reported by running rebase step."); + } + } + + private struct RebaseProgress + { + public long current; + public long total; + } + + /// + /// Returns the next rebase step, or null if there are none, + /// and the rebase operation needs to be finished. + /// + /// + /// + /// + private static RebaseProgress NextRebaseStep( + Repository repository, + RebaseHandle rebaseOperationHandle) + { + // stepBeingApplied indicates the step that will be applied by by git_rebase_next. + // The current step does not get incremented until git_rebase_next (except on + // the initial step), but we want to report the step that will be applied. + long stepToApplyIndex = Proxy.git_rebase_operation_current(rebaseOperationHandle); + + stepToApplyIndex++; + + long totalStepCount = Proxy.git_rebase_operation_entrycount(rebaseOperationHandle); + + if (stepToApplyIndex == totalStepCount) + { + stepToApplyIndex = -1; + } + + RebaseProgress progress = new RebaseProgress() + { + current = stepToApplyIndex, + total = totalStepCount, + }; + + return progress; + } + + private enum RebaseStepStatus + { + Committed, + Conflicts, + ChangesAlreadyApplied, + } + + private class RebaseStepResult + { + public RebaseStepResult(RebaseStepStatus status) + { + Status = status; + CommitId = GitOid.Empty; + } + + public RebaseStepResult(RebaseStepStatus status, GitOid commitId) + { + Status = status; + CommitId = commitId; + } + + /// + /// The ID of the commit that was generated, if any + /// + public GitOid CommitId; + + /// + /// bool to indicate if the patch was already applied. + /// If Patch was already applied, then CommitId will be empty (all zeros). + /// + public bool ChangesAlreadyApplied + { + get { return Status == RebaseStepStatus.ChangesAlreadyApplied; } + } + + public RebaseStepStatus Status; + } + } +} diff --git a/LibGit2Sharp/RebaseOptions.cs b/LibGit2Sharp/RebaseOptions.cs new file mode 100644 index 000000000..62cb6cbdb --- /dev/null +++ b/LibGit2Sharp/RebaseOptions.cs @@ -0,0 +1,58 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Options controlling rebase behavior. + /// + public sealed class RebaseOptions : IConvertableToGitCheckoutOpts + { + /// + /// Delegate that is called before each rebase step. + /// + public RebaseStepStartingHandler RebaseStepStarting { get; set; } + + /// + /// Delegate that is called after each rebase step is completed. + /// + public RebaseStepCompletedHandler RebaseStepCompleted { get; set; } + + /// + /// The Flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. + /// + public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } + + /// + /// Delegate that the checkout will report progress through. + /// + public CheckoutProgressHandler OnCheckoutProgress { get; set; } + + /// + /// Delegate that checkout will notify callers of + /// certain conditions. The conditions that are reported is + /// controlled with the CheckoutNotifyFlags property. + /// + public CheckoutNotifyHandler OnCheckoutNotify { get; set; } + + /// + /// How conflicting index entries should be written out during checkout. + /// + public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return CheckoutStrategy.GIT_CHECKOUT_SAFE | + GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); + } + } + } +} diff --git a/LibGit2Sharp/RebaseResult.cs b/LibGit2Sharp/RebaseResult.cs new file mode 100644 index 000000000..bee2254af --- /dev/null +++ b/LibGit2Sharp/RebaseResult.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// The status of the rebase. + /// + public enum RebaseStatus + { + /// + /// The rebase operation was run to completion + /// + Complete, + + /// + /// The rebase operation hit a conflict and stopped. + /// + Conflicts, + + /// + /// The rebase operation has hit a user requested stop point + /// (edit, reword, ect.) + /// + Stop, + }; + + /// + /// Information on a rebase operation. + /// + public class RebaseResult + { + /// + /// Needed for mocking. + /// + protected RebaseResult() + { } + + internal RebaseResult(RebaseStatus status, + long stepNumber, + long totalSteps, + RebaseStepInfo currentStepInfo) + { + Status = status; + CompletedStepCount = stepNumber; + TotalStepCount = totalSteps; + CurrentStepInfo = currentStepInfo; + } + + /// + /// Information on the operation to be performed in the current step. + /// If the overall Rebase operation has completed successfully, this will + /// be null. + /// + public virtual RebaseStepInfo CurrentStepInfo { get; private set; } + + /// + /// Did the rebase operation run until it should stop + /// (completed the rebase, or the operation for the current step + /// is one that sequencing should stop. + /// + public virtual RebaseStatus Status { get; protected set; } + + /// + /// The number of completed steps. + /// + public virtual long CompletedStepCount { get; protected set; } + + /// + /// The total number of steps in the rebase. + /// + public virtual long TotalStepCount { get; protected set; } + } +} diff --git a/LibGit2Sharp/RebaseStepInfo.cs b/LibGit2Sharp/RebaseStepInfo.cs new file mode 100644 index 000000000..4e3557696 --- /dev/null +++ b/LibGit2Sharp/RebaseStepInfo.cs @@ -0,0 +1,36 @@ +namespace LibGit2Sharp +{ + /// + /// Information on a particular step of a rebase operation. + /// + public class RebaseStepInfo + { + /// + /// Needed for mocking purposes. + /// + protected RebaseStepInfo() + { } + + internal RebaseStepInfo(RebaseStepOperation type, Commit commit, string exec) + { + Type = type; + Commit = commit; + Exec = exec; + } + + /// + /// The rebase operation type. + /// + public virtual RebaseStepOperation Type { get; private set; } + + /// + /// The object ID the step is operating on. + /// + public virtual Commit Commit { get; private set; } + + /// + /// Command to execute, if any. + /// + internal virtual string Exec { get; private set; } + } +} diff --git a/LibGit2Sharp/RecurseSubmodulesException.cs b/LibGit2Sharp/RecurseSubmodulesException.cs new file mode 100644 index 000000000..2269f0d16 --- /dev/null +++ b/LibGit2Sharp/RecurseSubmodulesException.cs @@ -0,0 +1,52 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when an error is encountered while recursing + /// through submodules. The inner exception contains the exception that was + /// initially thrown while operating on the submodule. + /// +#if NETFRAMEWORK + [Serializable] +#endif + public class RecurseSubmodulesException : LibGit2SharpException + { + /// + /// Initializes a new instance of the class. + /// + public RecurseSubmodulesException() + { } + + /// + /// The path to the initial repository the operation was run on. + /// + public virtual string InitialRepositoryPath { get; private set; } + + /// + /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. + /// The path to the initial repository the operation was performed on. + public RecurseSubmodulesException(string message, Exception innerException, string initialRepositoryPath) + : base(message, innerException) + { + InitialRepositoryPath = initialRepositoryPath; + } + +#if NETFRAMEWORK + /// + /// Initializes a new instance of the class with a serialized data. + /// + /// The that holds the serialized object data about the exception being thrown. + /// The that contains contextual information about the source or destination. + protected RecurseSubmodulesException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/RefSpec.cs b/LibGit2Sharp/RefSpec.cs index 5534bd547..4d9e28fbe 100644 --- a/LibGit2Sharp/RefSpec.cs +++ b/LibGit2Sharp/RefSpec.cs @@ -12,17 +12,16 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RefSpec { - private RefSpec(string refSpec, RefSpecDirection direction, string source, string destination, bool forceUpdate) - { - Ensure.ArgumentNotNullOrEmptyString(refSpec, "refSpec"); - Ensure.ArgumentNotNull(source, "source"); - Ensure.ArgumentNotNull(destination, "destination"); + // This is here to keep the pointer alive +#pragma warning disable 0414 + readonly Remote remote; +#pragma warning restore 0414 + readonly IntPtr handle; - Specification = refSpec; - Direction = direction; - Source = source; - Destination = destination; - ForceUpdate = forceUpdate; + internal unsafe RefSpec(Remote remote, git_refspec* handle) + { + this.remote = remote; + this.handle = new IntPtr(handle); } /// @@ -31,45 +30,106 @@ private RefSpec(string refSpec, RefSpecDirection direction, string source, strin protected RefSpec() { } - internal static RefSpec BuildFromPtr(GitRefSpecHandle handle) - { - Ensure.ArgumentNotNull(handle, "handle"); - - return new RefSpec(Proxy.git_refspec_string(handle), Proxy.git_refspec_direction(handle), - Proxy.git_refspec_src(handle), Proxy.git_refspec_dst(handle), Proxy.git_refspec_force(handle)); - } - /// /// Gets the pattern describing the mapping between remote and local references /// - public virtual string Specification { get; private set; } + public virtual string Specification + { + get + { + return Proxy.git_refspec_string(this.handle); + } + } /// /// Indicates whether this is intended to be used during a Push or Fetch operation /// - public virtual RefSpecDirection Direction { get; private set; } + public virtual RefSpecDirection Direction + { + get + { + return Proxy.git_refspec_direction(this.handle); + } + } /// /// The source reference specifier /// - public virtual string Source { get; private set; } + public virtual string Source + { + get + { + return Proxy.git_refspec_src(this.handle); + } + } /// /// The target reference specifier /// - public virtual string Destination { get; private set; } + public virtual string Destination + { + get + { + return Proxy.git_refspec_dst(this.handle); + } + } /// /// Indicates whether the destination will be force-updated if fast-forwarding is not possible /// - public virtual bool ForceUpdate { get; private set; } + public virtual bool ForceUpdate + { + get + { + return Proxy.git_refspec_force(this.handle); + } + } + + /// + /// Check whether the given reference matches the source (lhs) part of + /// this refspec. + /// + /// The reference name to check + public virtual bool SourceMatches(string reference) + { + return Proxy.git_refspec_src_matches(handle, reference); + } + + /// + /// Check whether the given reference matches the target (rhs) part of + /// this refspec. + /// + /// The reference name to check + public virtual bool DestinationMatches(string reference) + { + return Proxy.git_refspec_dst_matches(handle, reference); + } + + /// + /// Perform the transformation described by this refspec on the given + /// reference name (from source to destination). + /// + /// The reference name to transform + public virtual string Transform(string reference) + { + return Proxy.git_refspec_transform(handle, reference); + } + + /// + /// Perform the reverse of the transformation described by this refspec + /// on the given reference name (from destination to source). + /// + /// The reference name to transform + public virtual string ReverseTransform(string reference) + { + return Proxy.git_refspec_rtransform(handle, reference); + } private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0}", Specification); + return string.Format(CultureInfo.InvariantCulture, "{0}", Specification); } } } diff --git a/LibGit2Sharp/RefSpecCollection.cs b/LibGit2Sharp/RefSpecCollection.cs index 26042f9bf..a35710719 100644 --- a/LibGit2Sharp/RefSpecCollection.cs +++ b/LibGit2Sharp/RefSpecCollection.cs @@ -1,8 +1,9 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Diagnostics; using System.Globalization; +using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -14,7 +15,12 @@ namespace LibGit2Sharp [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RefSpecCollection : IEnumerable { - readonly IList refspecs; + // These are here to keep the pointer alive +#pragma warning disable 0414 + readonly Remote remote; + readonly RemoteHandle handle; +#pragma warning restore 0414 + readonly Lazy> refspecs; /// /// Needed for mocking purposes. @@ -22,28 +28,27 @@ public class RefSpecCollection : IEnumerable protected RefSpecCollection() { } - internal RefSpecCollection(RemoteSafeHandle handle) + internal RefSpecCollection(Remote remote, RemoteHandle handle) { Ensure.ArgumentNotNull(handle, "handle"); - refspecs = RetrieveRefSpecs(handle); + this.remote = remote; + this.handle = handle; + + refspecs = new Lazy>(() => RetrieveRefSpecs(remote, handle)); } - static IList RetrieveRefSpecs(RemoteSafeHandle remoteHandle) + static unsafe IList RetrieveRefSpecs(Remote remote, RemoteHandle remoteHandle) { int count = Proxy.git_remote_refspec_count(remoteHandle); List refSpecs = new List(); for (int i = 0; i < count; i++) { - using (GitRefSpecHandle handle = Proxy.git_remote_get_refspec(remoteHandle, i)) - { - refSpecs.Add(RefSpec.BuildFromPtr(handle)); - } + refSpecs.Add(new RefSpec(remote, Proxy.git_remote_get_refspec(remoteHandle, i))); } return refSpecs; - } /// @@ -52,7 +57,7 @@ static IList RetrieveRefSpecs(RemoteSafeHandle remoteHandle) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return refspecs.GetEnumerator(); + return refspecs.Value.GetEnumerator(); } /// @@ -68,8 +73,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/RefSpecDirection.cs b/LibGit2Sharp/RefSpecDirection.cs index 9239b7bd9..6c377a040 100644 --- a/LibGit2Sharp/RefSpecDirection.cs +++ b/LibGit2Sharp/RefSpecDirection.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Indicates whether a refspec is a push refspec or a fetch refspec diff --git a/LibGit2Sharp/Reference.cs b/LibGit2Sharp/Reference.cs index 8ab7d1b78..9a86195d1 100644 --- a/LibGit2Sharp/Reference.cs +++ b/LibGit2Sharp/Reference.cs @@ -25,29 +25,20 @@ public abstract class Reference : IEquatable, IBelongToARepository protected Reference() { } - /// - /// Initializes a new instance of the class. - /// - /// The canonical name. - /// The target identifier. - [Obsolete("This ctor will be removed in a future release.")] - protected Reference(string canonicalName, string targetIdentifier) - : this(null, canonicalName, targetIdentifier) - { - } - - /// - /// This would be protected+internal, were that supported by C#. - /// Do not use except in subclasses. - /// - internal Reference(IRepository repo, string canonicalName, string targetIdentifier) + private protected Reference(IRepository repo, string canonicalName, string targetIdentifier) { this.repo = repo; this.canonicalName = canonicalName; this.targetIdentifier = targetIdentifier; } - internal static T BuildFromPtr(ReferenceSafeHandle handle, Repository repo) where T : Reference + // This overload lets public-facing methods avoid having to use the pointers directly + internal static unsafe T BuildFromPtr(ReferenceHandle handle, Repository repo) where T : Reference + { + return BuildFromPtr((git_reference*)handle.AsIntPtr(), repo); + } + + internal static unsafe T BuildFromPtr(git_reference* handle, Repository repo) where T : Reference { GitReferenceType type = Proxy.git_reference_type(handle); string name = Proxy.git_reference_name(handle); @@ -70,7 +61,7 @@ internal static T BuildFromPtr(ReferenceSafeHandle handle, Repository repo) w break; default: - throw new LibGit2SharpException(String.Format(CultureInfo.InvariantCulture, "Unable to build a new reference from a type '{0}'.", type)); + throw new LibGit2SharpException("Unable to build a new reference from a type '{0}'.", type); } return reference as T; @@ -94,6 +85,42 @@ public static bool IsValidName(string canonicalName) return Proxy.git_reference_is_valid_name(canonicalName); } + /// + /// Determine if the current is a local branch. + /// + /// true if the current is a local branch, false otherwise. + public virtual bool IsLocalBranch + { + get { return CanonicalName.LooksLikeLocalBranch(); } + } + + /// + /// Determine if the current is a remote tracking branch. + /// + /// true if the current is a remote tracking branch, false otherwise. + public virtual bool IsRemoteTrackingBranch + { + get { return CanonicalName.LooksLikeRemoteTrackingBranch(); } + } + + /// + /// Determine if the current is a tag. + /// + /// true if the current is a tag, false otherwise. + public virtual bool IsTag + { + get { return CanonicalName.LooksLikeTag(); } + } + + /// + /// Determine if the current is a note. + /// + /// true if the current is a note, false otherwise. + public virtual bool IsNote + { + get { return CanonicalName.LooksLikeNote(); } + } + /// /// Gets the full name of this reference. /// @@ -122,10 +149,10 @@ public virtual string TargetIdentifier } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as Reference); @@ -173,7 +200,7 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// /// The that represents the current . public override string ToString() @@ -206,10 +233,23 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => \"{1}\"", CanonicalName, TargetIdentifier); + "{0} => \"{1}\"", + CanonicalName, + TargetIdentifier); } } - IRepository IBelongToARepository.Repository { get { return repo; } } + IRepository IBelongToARepository.Repository + { + get + { + if (repo == null) + { + throw new InvalidOperationException("Repository requires a local repository"); + } + + return repo; + } + } } } diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index a6d89c9e0..92bf85426 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -66,26 +66,148 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the when adding the + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, + string logMessage) + { + return Add(name, canonicalRefNameOrObjectish, logMessage, false); + } + + private enum RefState + { + Exists, + DoesNotExistButLooksValid, + DoesNotLookValid, + } + + private static RefState TryResolveReference(out Reference reference, ReferenceCollection refsColl, string canonicalName) + { + if (!Reference.IsValidName(canonicalName)) + { + reference = null; + return RefState.DoesNotLookValid; + } + + reference = refsColl[canonicalName]; + + return reference != null ? RefState.Exists : RefState.DoesNotExistButLooksValid; + } + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the when adding the + /// True to allow silent overwriting a potentially existing reference, false otherwise. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, string logMessage, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); + + Reference reference; + RefState refState = TryResolveReference(out reference, this, canonicalRefNameOrObjectish); + + var gitObject = repo.Lookup(canonicalRefNameOrObjectish, GitObjectType.Any, LookUpOptions.None); + + if (refState == RefState.Exists) + { + return Add(name, reference, logMessage, allowOverwrite); + } + + if (refState == RefState.DoesNotExistButLooksValid && gitObject == null) + { + using (ReferenceHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, name, canonicalRefNameOrObjectish, allowOverwrite, + logMessage)) + { + return Reference.BuildFromPtr(handle, repo); + } + } + + Ensure.GitObjectIsNotNull(gitObject, canonicalRefNameOrObjectish); + + if (logMessage == null) + { + logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: Created from {1}", + name.LooksLikeLocalBranch() ? "branch" : "reference", canonicalRefNameOrObjectish); + } + + EnsureHasLog(name); + return Add(name, gitObject.Id, logMessage, allowOverwrite); + } + + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish) + { + return Add(name, canonicalRefNameOrObjectish, null, false); + } + + /// + /// Creates a direct or symbolic reference with the specified name and target + /// + /// The name of the reference to create. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// True to allow silent overwriting a potentially existing reference, false otherwise. + /// A new . + public virtual Reference Add(string name, string canonicalRefNameOrObjectish, bool allowOverwrite) + { + return Add(name, canonicalRefNameOrObjectish, null, allowOverwrite); + } + /// + /// Creates a direct reference with the specified name and target + /// + /// The canonical name of the reference to create. + /// Id of the target object. + /// The optional message to log in the when adding the + /// A new . + public virtual DirectReference Add(string name, ObjectId targetId, string logMessage) + { + return Add(name, targetId, logMessage, false); + } + /// /// Creates a direct reference with the specified name and target /// /// The canonical name of the reference to create. /// Id of the target object. - /// Identity used for updating the reflog. /// The optional message to log in the when adding the /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual DirectReference Add(string name, ObjectId targetId, Signature signature, string logMessage, bool allowOverwrite = false) + public virtual DirectReference Add(string name, ObjectId targetId, string logMessage, bool allowOverwrite) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetId, "targetId"); - using (ReferenceSafeHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, signature.OrDefault(repo.Config), logMessage)) + using (ReferenceHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, logMessage)) { return (DirectReference)Reference.BuildFromPtr(handle, repo); } } + /// + /// Creates a direct reference with the specified name and target + /// + /// The canonical name of the reference to create. + /// Id of the target object. + /// A new . + public virtual DirectReference Add(string name, ObjectId targetId) + { + return Add(name, targetId, null, false); + } + /// /// Creates a direct reference with the specified name and target /// @@ -93,9 +215,21 @@ public virtual DirectReference Add(string name, ObjectId targetId, Signature sig /// Id of the target object. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual DirectReference Add(string name, ObjectId targetId, bool allowOverwrite = false) + public virtual DirectReference Add(string name, ObjectId targetId, bool allowOverwrite) { - return Add(name, targetId, null, null, allowOverwrite); + return Add(name, targetId, null, allowOverwrite); + } + + /// + /// Creates a symbolic reference with the specified name and target + /// + /// The canonical name of the reference to create. + /// The target reference. + /// The optional message to log in the when adding the + /// A new . + public virtual SymbolicReference Add(string name, Reference targetRef, string logMessage) + { + return Add(name, targetRef, logMessage, false); } /// @@ -103,22 +237,35 @@ public virtual DirectReference Add(string name, ObjectId targetId, bool allowOve /// /// The canonical name of the reference to create. /// The target reference. - /// Identity used for updating the reflog. /// The optional message to log in the when adding the /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual SymbolicReference Add(string name, Reference targetRef, Signature signature, string logMessage, bool allowOverwrite = false) + public virtual SymbolicReference Add(string name, Reference targetRef, string logMessage, bool allowOverwrite) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetRef, "targetRef"); - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, name, targetRef.CanonicalName, - allowOverwrite, signature.OrDefault(repo.Config), logMessage)) + using (ReferenceHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, + name, + targetRef.CanonicalName, + allowOverwrite, + logMessage)) { return (SymbolicReference)Reference.BuildFromPtr(handle, repo); } } + /// + /// Creates a symbolic reference with the specified name and target + /// + /// The canonical name of the reference to create. + /// The target reference. + /// A new . + public virtual SymbolicReference Add(string name, Reference targetRef) + { + return Add(name, targetRef, null, false); + } + /// /// Creates a symbolic reference with the specified name and target /// @@ -126,9 +273,27 @@ public virtual SymbolicReference Add(string name, Reference targetRef, Signature /// The target reference. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual SymbolicReference Add(string name, Reference targetRef, bool allowOverwrite = false) + public virtual SymbolicReference Add(string name, Reference targetRef, bool allowOverwrite) { - return Add(name, targetRef, null, null, allowOverwrite); + return Add(name, targetRef, null, allowOverwrite); + } + + /// + /// Remove a reference with the specified name + /// + /// The canonical name of the reference to delete. + public virtual void Remove(string name) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + Reference reference = this[name]; + + if (reference == null) + { + return; + } + + Remove(reference); } /// @@ -147,54 +312,114 @@ public virtual void Remove(Reference reference) /// /// The reference to rename. /// The new canonical name. - /// Identity used for updating the reflog. + /// Message added to the reflog. + /// A new . + public virtual Reference Rename(Reference reference, string newName, string logMessage) + { + return Rename(reference, newName, logMessage, false); + } + + /// + /// Rename an existing reference with a new name, and update the reflog + /// + /// The reference to rename. + /// The new canonical name. /// Message added to the reflog. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - [Obsolete("This method will be removed in the next release. Please use Rename() instead.")] - public virtual Reference Move(Reference reference, string newName, Signature signature, string logMessage = null, bool allowOverwrite = false) + public virtual Reference Rename(Reference reference, string newName, string logMessage, bool allowOverwrite) { - return Rename(reference, newName, signature, logMessage, allowOverwrite); + Ensure.ArgumentNotNull(reference, "reference"); + Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); + + if (logMessage == null) + { + logMessage = string.Format(CultureInfo.InvariantCulture, + "{0}: renamed {1} to {2}", + reference.IsLocalBranch + ? "branch" + : "reference", + reference.CanonicalName, + newName); + } + + using (ReferenceHandle referencePtr = RetrieveReferencePtr(reference.CanonicalName)) + using (ReferenceHandle handle = Proxy.git_reference_rename(referencePtr, newName, allowOverwrite, logMessage)) + { + return Reference.BuildFromPtr(handle, repo); + } } /// /// Rename an existing reference with a new name /// - /// The reference to rename. + /// The canonical name of the reference to rename. + /// The new canonical name. + /// A new . + public virtual Reference Rename(string currentName, string newName) + { + return Rename(currentName, newName, null, false); + } + + /// + /// Rename an existing reference with a new name + /// + /// The canonical name of the reference to rename. /// The new canonical name. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - [Obsolete("This method will be removed in the next release. Please use Rename() instead.")] - public virtual Reference Move(Reference reference, string newName, bool allowOverwrite = false) + public virtual Reference Rename(string currentName, string newName, + bool allowOverwrite) { - return Rename(reference, newName, null, null, allowOverwrite); + return Rename(currentName, newName, null, allowOverwrite); } /// - /// Rename an existing reference with a new name, and update the reflog + /// Rename an existing reference with a new name /// - /// The reference to rename. + /// The canonical name of the reference to rename. /// The new canonical name. - /// Identity used for updating the reflog. - /// Message added to the reflog. + /// The optional message to log in the + /// A new . + public virtual Reference Rename(string currentName, string newName, + string logMessage) + { + return Rename(currentName, newName, logMessage, false); + } + + /// + /// Rename an existing reference with a new name + /// + /// The canonical name of the reference to rename. + /// The new canonical name. + /// The optional message to log in the /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual Reference Rename(Reference reference, string newName, Signature signature, string logMessage = null, bool allowOverwrite = false) + public virtual Reference Rename(string currentName, string newName, + string logMessage, bool allowOverwrite) { - Ensure.ArgumentNotNull(reference, "reference"); - Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); + Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); - if (logMessage == null) - { - logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: renamed {1} to {2}", - reference.IsLocalBranch() ? "branch" : "reference", reference.CanonicalName, newName); - } + Reference reference = this[currentName]; - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(reference.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_rename(referencePtr, newName, allowOverwrite, signature.OrDefault(repo.Config), logMessage)) + if (reference == null) { - return Reference.BuildFromPtr(handle, repo); + throw new LibGit2SharpException("Reference '{0}' doesn't exist. One cannot move a non existing reference.", + currentName); } + + return Rename(reference, newName, logMessage, allowOverwrite); + } + + /// + /// Rename an existing reference with a new name + /// + /// The reference to rename. + /// The new canonical name. + /// A new . + public virtual Reference Rename(Reference reference, string newName) + { + return Rename(reference, newName, null, false); } /// @@ -204,18 +429,20 @@ public virtual Reference Rename(Reference reference, string newName, Signature s /// The new canonical name. /// True to allow silent overwriting a potentially existing reference, false otherwise. /// A new . - public virtual Reference Rename(Reference reference, string newName, bool allowOverwrite = false) + public virtual Reference Rename(Reference reference, string newName, bool allowOverwrite) { - return Rename(reference, newName, null, null, allowOverwrite); + return Rename(reference, newName, null, allowOverwrite); } internal T Resolve(string name) where T : Reference { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(name, false)) + using (ReferenceHandle referencePtr = RetrieveReferencePtr(name, false)) { - return referencePtr == null ? null : Reference.BuildFromPtr(referencePtr, repo); + return referencePtr == null + ? null + : Reference.BuildFromPtr(referencePtr, repo); } } @@ -224,28 +451,116 @@ internal T Resolve(string name) where T : Reference /// /// The direct reference which target should be updated. /// The new target. - /// The identity used for updating the reflog. /// The optional message to log in the of the reference /// A new . - public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, Signature signature, string logMessage) + public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, string logMessage) { Ensure.ArgumentNotNull(directRef, "directRef"); Ensure.ArgumentNotNull(targetId, "targetId"); - signature = signature.OrDefault(repo.Config); - if (directRef.CanonicalName == "HEAD") { - return UpdateHeadTarget(targetId, signature, logMessage); + return UpdateHeadTarget(targetId, logMessage); } - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(directRef.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_set_target(referencePtr, targetId, signature, logMessage)) + return UpdateDirectReferenceTarget(directRef, targetId, logMessage); + } + + private Reference UpdateDirectReferenceTarget(Reference directRef, ObjectId targetId, string logMessage) + { + using (ReferenceHandle referencePtr = RetrieveReferencePtr(directRef.CanonicalName)) + using (ReferenceHandle handle = Proxy.git_reference_set_target(referencePtr, targetId, logMessage)) { return Reference.BuildFromPtr(handle, repo); } } + /// + /// Updates the target of a direct reference. + /// + /// The direct reference which target should be updated. + /// The revparse spec of the target. + /// The optional message to log in the + /// A new . + public virtual Reference UpdateTarget(Reference directRef, string objectish, string logMessage) + { + Ensure.ArgumentNotNull(directRef, "directRef"); + Ensure.ArgumentNotNull(objectish, "objectish"); + + GitObject target = repo.Lookup(objectish); + + Ensure.GitObjectIsNotNull(target, objectish); + + return UpdateTarget(directRef, target.Id, logMessage); + } + + /// + /// Updates the target of a direct reference + /// + /// The direct reference which target should be updated. + /// The revparse spec of the target. + /// A new . + public virtual Reference UpdateTarget(Reference directRef, string objectish) + { + return UpdateTarget(directRef, objectish, null); + } + + /// + /// Updates the target of a reference + /// + /// The canonical name of the reference. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// The optional message to log in the of the reference. + /// A new . + public virtual Reference UpdateTarget(string name, string canonicalRefNameOrObjectish, string logMessage) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); + + if (name == "HEAD") + { + return UpdateHeadTarget(canonicalRefNameOrObjectish, logMessage); + } + + Reference reference = this[name]; + + var directReference = reference as DirectReference; + if (directReference != null) + { + return UpdateTarget(directReference, canonicalRefNameOrObjectish, logMessage); + } + + var symbolicReference = reference as SymbolicReference; + if (symbolicReference != null) + { + Reference targetRef; + + RefState refState = TryResolveReference(out targetRef, this, canonicalRefNameOrObjectish); + + if (refState == RefState.DoesNotLookValid) + { + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is a Symbolic reference, you must provide a reference canonical name as the target.", name), nameof(canonicalRefNameOrObjectish)); + } + + return UpdateTarget(symbolicReference, targetRef, logMessage); + } + + throw new LibGit2SharpException("Reference '{0}' has an unexpected type ('{1}').", + name, + reference.GetType()); + } + + /// + /// Updates the target of a reference + /// + /// The canonical name of the reference. + /// The target which can be either the canonical name of a reference or a revparse spec. + /// A new . + public virtual Reference UpdateTarget(string name, string canonicalRefNameOrObjectish) + { + return UpdateTarget(name, canonicalRefNameOrObjectish, null); + } + /// /// Updates the target of a direct reference /// @@ -254,7 +569,7 @@ public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, Si /// A new . public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId) { - return UpdateTarget(directRef, targetId, null, null); + return UpdateTarget(directRef, targetId, null); } /// @@ -262,23 +577,25 @@ public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId) /// /// The symbolic reference which target should be updated. /// The new target. - /// The identity used for updating the reflog. /// The optional message to log in the of the reference. /// A new . - public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef, Signature signature, string logMessage) + public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) { Ensure.ArgumentNotNull(symbolicRef, "symbolicRef"); Ensure.ArgumentNotNull(targetRef, "targetRef"); - signature = signature.OrDefault(repo.Config); - if (symbolicRef.CanonicalName == "HEAD") { - return UpdateHeadTarget(targetRef, signature, logMessage); + return UpdateHeadTarget(targetRef, logMessage); } - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(symbolicRef.CanonicalName)) - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_set_target(referencePtr, targetRef.CanonicalName, signature, logMessage)) + return UpdateSymbolicRefenceTarget(symbolicRef, targetRef, logMessage); + } + + private Reference UpdateSymbolicRefenceTarget(Reference symbolicRef, Reference targetRef, string logMessage) + { + using (ReferenceHandle referencePtr = RetrieveReferencePtr(symbolicRef.CanonicalName)) + using (ReferenceHandle handle = Proxy.git_reference_symbolic_set_target(referencePtr, targetRef.CanonicalName, logMessage)) { return Reference.BuildFromPtr(handle, repo); } @@ -292,52 +609,71 @@ public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef /// A new . public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef) { - return UpdateTarget(symbolicRef, targetRef, null, null); + return UpdateTarget(symbolicRef, targetRef, null); } - internal Reference UpdateHeadTarget(T target, Signature signature, string logMessage) + internal Reference MoveHeadTarget(T target) { - Debug.Assert(signature != null); - if (target is ObjectId) { - Proxy.git_repository_set_head_detached(repo.Handle, target as ObjectId, signature, logMessage); + Proxy.git_repository_set_head_detached(repo.Handle, target as ObjectId); } else if (target is DirectReference || target is SymbolicReference) { - Proxy.git_repository_set_head(repo.Handle, (target as Reference).CanonicalName, signature, logMessage); + Proxy.git_repository_set_head(repo.Handle, (target as Reference).CanonicalName); } else if (target is string) { var targetIdentifier = target as string; - if (Reference.IsValidName(targetIdentifier)) + if (Reference.IsValidName(targetIdentifier) && targetIdentifier.LooksLikeLocalBranch()) { - Proxy.git_repository_set_head(repo.Handle, targetIdentifier, signature, logMessage); + Proxy.git_repository_set_head(repo.Handle, targetIdentifier); } else { - GitObject commit = repo.Lookup(targetIdentifier, - GitObjectType.Any, - LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | - LookUpOptions.DereferenceResultToCommit | - LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); - - Proxy.git_repository_set_head_detached(repo.Handle, commit.Id, signature, logMessage); + using (var annotatedCommit = Proxy.git_annotated_commit_from_revspec(repo.Handle, targetIdentifier)) + { + Proxy.git_repository_set_head_detached_from_annotated(repo.Handle, annotatedCommit); + } } } else { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "'{0}' is not a valid target type.", typeof (T))); + "'{0}' is not a valid target type.", + typeof(T))); } return repo.Refs.Head; } - internal ReferenceSafeHandle RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotFound = true) + internal Reference UpdateHeadTarget(ObjectId target, string logMessage) + { + Add("HEAD", target, logMessage, true); + + return repo.Refs.Head; + } + + internal Reference UpdateHeadTarget(Reference target, string logMessage) + { + Ensure.ArgumentConformsTo(target, r => (r is DirectReference || r is SymbolicReference), "target"); + + Add("HEAD", target, logMessage, true); + + return repo.Refs.Head; + } + + internal Reference UpdateHeadTarget(string target, string logMessage) + { + this.Add("HEAD", target, logMessage, true); + + return repo.Refs.Head; + } + + internal ReferenceHandle RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotFound = true) { - ReferenceSafeHandle reference = Proxy.git_reference_lookup(repo.Handle, referenceName, shouldThrowIfNotFound); + ReferenceHandle reference = Proxy.git_reference_lookup(repo.Handle, referenceName, shouldThrowIfNotFound); return reference; } @@ -367,12 +703,83 @@ public virtual Reference Head get { return this["HEAD"]; } } + + /// + /// Find the s among + /// that can reach at least one in the specified . + /// + /// The set of s to examine. + /// The set of s that are interesting. + /// A subset of that can reach at least one within . + public virtual IEnumerable ReachableFrom( + IEnumerable refSubset, + IEnumerable targets) + { + Ensure.ArgumentNotNull(refSubset, "refSubset"); + Ensure.ArgumentNotNull(targets, "targets"); + + var refs = new List(refSubset); + if (refs.Count == 0) + { + return Enumerable.Empty(); + } + + List targetsSet = targets.Select(c => c.Id).Distinct().ToList(); + if (targetsSet.Count == 0) + { + return Enumerable.Empty(); + } + + var result = new List(); + + foreach (var reference in refs) + { + var peeledTargetCommit = reference + .ResolveToDirectReference() + .Target.Peel(false); + + if (peeledTargetCommit == null) + { + continue; + } + + var commitId = peeledTargetCommit.Id; + + foreach (var potentialAncestorId in targetsSet) + { + if (potentialAncestorId == commitId) + { + result.Add(reference); + break; + } + + if (Proxy.git_graph_descendant_of(repo.Handle, commitId, potentialAncestorId)) + { + result.Add(reference); + break; + } + } + } + + return result; + } + + /// + /// Find the s + /// that can reach at least one in the specified . + /// + /// The set of s that are interesting. + /// The list of that can reach at least one within . + public virtual IEnumerable ReachableFrom(IEnumerable targets) + { + return ReachableFrom(this, targets); + } + private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } diff --git a/LibGit2Sharp/ReferenceCollectionExtensions.cs b/LibGit2Sharp/ReferenceCollectionExtensions.cs deleted file mode 100644 index 962a5af94..000000000 --- a/LibGit2Sharp/ReferenceCollectionExtensions.cs +++ /dev/null @@ -1,325 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ReferenceCollectionExtensions - { - private enum RefState - { - Exists, - DoesNotExistButLooksValid, - DoesNotLookValid, - } - - private static RefState TryResolveReference(out Reference reference, ReferenceCollection refsColl, string canonicalName) - { - if (!Reference.IsValidName(canonicalName)) - { - reference = null; - return RefState.DoesNotLookValid; - } - - reference = refsColl[canonicalName]; - - return reference != null ? RefState.Exists : RefState.DoesNotExistButLooksValid; - } - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// The identity used for updating the reflog - /// The optional message to log in the when adding the - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, Signature signature, string logMessage, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); - - Reference reference; - RefState refState = TryResolveReference(out reference, refsColl, canonicalRefNameOrObjectish); - - var gitObject = refsColl.repo.Lookup(canonicalRefNameOrObjectish, GitObjectType.Any, LookUpOptions.None); - - if (refState == RefState.Exists) - { - return refsColl.Add(name, reference, signature, logMessage, allowOverwrite); - } - - if (refState == RefState.DoesNotExistButLooksValid && gitObject == null) - { - using (ReferenceSafeHandle handle = Proxy.git_reference_symbolic_create(refsColl.repo.Handle, name, canonicalRefNameOrObjectish, allowOverwrite, - signature.OrDefault(refsColl.repo.Config), logMessage)) - { - return Reference.BuildFromPtr(handle, refsColl.repo); - } - } - - Ensure.GitObjectIsNotNull(gitObject, canonicalRefNameOrObjectish); - - if (logMessage == null) - { - logMessage = string.Format(CultureInfo.InvariantCulture, "{0}: Created from {1}", - name.LooksLikeLocalBranch() ? "branch" : "reference", canonicalRefNameOrObjectish); - } - - refsColl.EnsureHasLog(name); - return refsColl.Add(name, gitObject.Id, signature, logMessage, allowOverwrite); - } - - /// - /// Creates a direct or symbolic reference with the specified name and target - /// - /// The being worked with. - /// The name of the reference to create. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, bool allowOverwrite = false) - { - return Add(refsColl, name, canonicalRefNameOrObjectish, null, null, allowOverwrite); - } - - /// - /// Updates the target of a direct reference. - /// - /// The being worked with. - /// The direct reference which target should be updated. - /// The revparse spec of the target. - /// The identity used for updating the reflog - /// The optional message to log in the - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, Reference directRef, string objectish, Signature signature, string logMessage) - { - Ensure.ArgumentNotNull(directRef, "directRef"); - Ensure.ArgumentNotNull(objectish, "objectish"); - - GitObject target = refsColl.repo.Lookup(objectish); - - Ensure.GitObjectIsNotNull(target, objectish); - - return refsColl.UpdateTarget(directRef, target.Id, signature, logMessage); - } - - /// - /// Updates the target of a direct reference - /// - /// The being worked with. - /// The direct reference which target should be updated. - /// The revparse spec of the target. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, Reference directRef, string objectish) - { - return UpdateTarget(refsColl, directRef, objectish, null, null); - } - - /// - /// Rename an existing reference with a new name - /// - /// The canonical name of the reference to rename. - /// The new canonical name. - /// The identity used for updating the reflog - /// The optional message to log in the - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// The being worked with. - /// A new . - [Obsolete("This method will be removed in the next release. Please use Rename() instead.")] - public static Reference Move(this ReferenceCollection refsColl, string currentName, string newName, - Signature signature = null, string logMessage = null, bool allowOverwrite = false) - { - return refsColl.Rename(currentName, newName, signature, logMessage, allowOverwrite); - } - - /// - /// Rename an existing reference with a new name - /// - /// The canonical name of the reference to rename. - /// The new canonical name. - /// The identity used for updating the reflog - /// The optional message to log in the - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// The being worked with. - /// A new . - public static Reference Rename(this ReferenceCollection refsColl, string currentName, string newName, - Signature signature = null, string logMessage = null, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); - - Reference reference = refsColl[currentName]; - - if (reference == null) - { - throw new LibGit2SharpException( - string.Format(CultureInfo.InvariantCulture, - "Reference '{0}' doesn't exist. One cannot move a non existing reference.", currentName)); - } - - return refsColl.Rename(reference, newName, signature, logMessage, allowOverwrite); - } - - /// - /// Updates the target of a reference - /// - /// The being worked with. - /// The canonical name of the reference. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// The identity used for updating the reflog - /// The optional message to log in the of the reference. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, Signature signature, string logMessage) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); - - signature = signature.OrDefault(refsColl.repo.Config); - - if (name == "HEAD") - { - return refsColl.UpdateHeadTarget(canonicalRefNameOrObjectish, signature, logMessage); - } - - Reference reference = refsColl[name]; - - var directReference = reference as DirectReference; - if (directReference != null) - { - return refsColl.UpdateTarget(directReference, canonicalRefNameOrObjectish, signature, logMessage); - } - - var symbolicReference = reference as SymbolicReference; - if (symbolicReference != null) - { - Reference targetRef; - - RefState refState = TryResolveReference(out targetRef, refsColl, canonicalRefNameOrObjectish); - - if (refState == RefState.DoesNotLookValid) - { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is a Symbolic reference, you must provide a reference canonical name as the target.", name), "canonicalRefNameOrObjectish"); - } - - return refsColl.UpdateTarget(symbolicReference, targetRef, signature, logMessage); - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Reference '{0}' has an unexpected type ('{1}').", name, reference.GetType())); - } - - /// - /// Updates the target of a reference - /// - /// The being worked with. - /// The canonical name of the reference. - /// The target which can be either the canonical name of a reference or a revparse spec. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish) - { - return UpdateTarget(refsColl, name, canonicalRefNameOrObjectish, null, null); - } - - /// - /// Delete a reference with the specified name - /// - /// The being worked with. - /// The canonical name of the reference to delete. - public static void Remove(this ReferenceCollection refsColl, string name) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - - Reference reference = refsColl[name]; - - if (reference == null) - { - return; - } - - refsColl.Remove(reference); - } - - /// - /// Find the s among - /// that can reach at least one in the specified . - /// - /// The being worked with. - /// The set of s to examine. - /// The set of s that are interesting. - /// A subset of that can reach at least one within . - public static IEnumerable ReachableFrom( - this ReferenceCollection refsColl, - IEnumerable refSubset, - IEnumerable targets) - { - Ensure.ArgumentNotNull(refSubset, "refSubset"); - Ensure.ArgumentNotNull(targets, "targets"); - - var refs = new List(refSubset); - if (refs.Count == 0) - { - return Enumerable.Empty(); - } - - List targetsSet = targets.Select(c => c.Id).Distinct().ToList(); - if (targetsSet.Count == 0) - { - return Enumerable.Empty(); - } - - var result = new List(); - - foreach (var reference in refs) - { - var peeledTargetCommit = reference - .ResolveToDirectReference() - .Target.DereferenceToCommit(false); - - if (peeledTargetCommit == null) - { - continue; - } - - var commitId = peeledTargetCommit.Id; - - foreach (var potentialAncestorId in targetsSet) - { - if (potentialAncestorId == commitId) - { - result.Add(reference); - break; - } - - if (Proxy.git_graph_descendant_of(refsColl.repo.Handle, commitId, potentialAncestorId)) - { - result.Add(reference); - break; - } - } - } - - return result; - } - - /// - /// Find the s - /// that can reach at least one in the specified . - /// - /// The being worked with. - /// The set of s that are interesting. - /// The list of that can reach at least one within . - public static IEnumerable ReachableFrom( - this ReferenceCollection refsColl, - IEnumerable targets) - { - return ReachableFrom(refsColl, refsColl, targets); - } - } -} diff --git a/LibGit2Sharp/ReferenceExtensions.cs b/LibGit2Sharp/ReferenceExtensions.cs index 8b33e1618..c29f6f076 100644 --- a/LibGit2Sharp/ReferenceExtensions.cs +++ b/LibGit2Sharp/ReferenceExtensions.cs @@ -5,7 +5,7 @@ namespace LibGit2Sharp /// /// Provides helpers to a . /// - public static class ReferenceExtensions + internal static class ReferenceExtensions { internal static bool LooksLikeLocalBranch(this string canonicalName) { @@ -31,45 +31,5 @@ private static bool IsPrefixedBy(this string input, string prefix) { return input.StartsWith(prefix, StringComparison.Ordinal); } - - /// - /// Determine if the current is a local branch. - /// - /// The to test. - /// true if the current is a local branch, false otherwise. - public static bool IsLocalBranch(this Reference reference) - { - return reference.CanonicalName.LooksLikeLocalBranch(); - } - - /// - /// Determine if the current is a remote tracking branch. - /// - /// The to test. - /// true if the current is a remote tracking branch, false otherwise. - public static bool IsRemoteTrackingBranch(this Reference reference) - { - return reference.CanonicalName.LooksLikeRemoteTrackingBranch(); - } - - /// - /// Determine if the current is a tag. - /// - /// The to test. - /// true if the current is a tag, false otherwise. - public static bool IsTag(this Reference reference) - { - return reference.CanonicalName.LooksLikeTag(); - } - - /// - /// Determine if the current is a note. - /// - /// The to test. - /// true if the current is a note, false otherwise. - public static bool IsNote(this Reference reference) - { - return reference.CanonicalName.LooksLikeNote(); - } } } diff --git a/LibGit2Sharp/ReferenceWrapper.cs b/LibGit2Sharp/ReferenceWrapper.cs index 583c68d4f..7fb8497c6 100644 --- a/LibGit2Sharp/ReferenceWrapper.cs +++ b/LibGit2Sharp/ReferenceWrapper.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using LibGit2Sharp.Core; @@ -10,16 +11,21 @@ namespace LibGit2Sharp /// /// The type of the referenced Git object. [DebuggerDisplay("{DebuggerDisplay,nq}")] +#if NET + public abstract class ReferenceWrapper<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TObject> : IEquatable>, IBelongToARepository where TObject : GitObject +#else public abstract class ReferenceWrapper : IEquatable>, IBelongToARepository where TObject : GitObject +#endif { /// /// The repository. /// protected readonly Repository repo; + private readonly Reference reference; private readonly Lazy objectBuilder; private static readonly LambdaEqualityHelper> equalityHelper = - new LambdaEqualityHelper>(x => x.CanonicalName, x => x.TargetObject); + new LambdaEqualityHelper>(x => x.CanonicalName, x => x.reference.TargetIdentifier); private readonly string canonicalName; @@ -40,6 +46,7 @@ protected internal ReferenceWrapper(Repository repo, Reference reference, Func(() => RetrieveTargetObject(reference)); } @@ -52,13 +59,24 @@ public virtual string CanonicalName } /// - /// Gets the name of this reference. + /// Gets the human-friendly name of this reference. /// - public virtual string Name + public virtual string FriendlyName { get { return Shorten(); } } + /// + /// The underlying + /// + public virtual Reference Reference + { + get + { + return reference; + } + } + /// /// Returns the , a representation of the current reference. /// @@ -111,10 +129,10 @@ public bool Equals(ReferenceWrapper other) } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as ReferenceWrapper); @@ -156,8 +174,10 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => \"{1}\"", CanonicalName, - (TargetObject != null) ? TargetObject.Id.ToString(7) : "?"); + "{0} => \"{1}\"", CanonicalName, + (TargetObject != null) + ? TargetObject.Id.ToString(7) + : "?"); } } diff --git a/LibGit2Sharp/ReflogCollection.cs b/LibGit2Sharp/ReflogCollection.cs index d95efbcb7..20b1a8b73 100644 --- a/LibGit2Sharp/ReflogCollection.cs +++ b/LibGit2Sharp/ReflogCollection.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -38,8 +37,7 @@ internal ReflogCollection(Repository repo, string canonicalName) if (!Reference.IsValidName(canonicalName)) { - throw new InvalidSpecificationException( - string.Format(CultureInfo.InvariantCulture, "The given reference name '{0}' is not valid", canonicalName)); + throw new InvalidSpecificationException("The given reference name '{0}' is not valid", canonicalName); } this.repo = repo; @@ -55,17 +53,17 @@ internal ReflogCollection(Repository repo, string canonicalName) /// /// /// An object that can be used to iterate through the collection. - public virtual IEnumerator GetEnumerator() + public virtual unsafe IEnumerator GetEnumerator() { var entries = new List(); - using (ReflogSafeHandle reflog = Proxy.git_reflog_read(repo.Handle, canonicalName)) + using (ReflogHandle reflog = Proxy.git_reflog_read(repo.Handle, canonicalName)) { var entriesCount = Proxy.git_reflog_entrycount(reflog); for (int i = 0; i < entriesCount; i++) { - ReflogEntrySafeHandle handle = Proxy.git_reflog_entry_byindex(reflog, i); + git_reflog_entry* handle = Proxy.git_reflog_entry_byindex(reflog, i); entries.Add(new ReflogEntry(handle)); } } @@ -88,8 +86,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/ReflogEntry.cs b/LibGit2Sharp/ReflogEntry.cs index def2e0b3e..d5f064c5a 100644 --- a/LibGit2Sharp/ReflogEntry.cs +++ b/LibGit2Sharp/ReflogEntry.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -11,7 +12,7 @@ public class ReflogEntry { private readonly ObjectId _from; private readonly ObjectId _to; - private readonly Signature _commiter; + private readonly Signature _committer; private readonly string message; /// @@ -24,11 +25,11 @@ protected ReflogEntry() /// Initializes a new instance of the class. /// /// a to the reflog entry - public ReflogEntry(SafeHandle entryHandle) + internal unsafe ReflogEntry(git_reflog_entry* entryHandle) { _from = Proxy.git_reflog_entry_id_old(entryHandle); _to = Proxy.git_reflog_entry_id_new(entryHandle); - _commiter = Proxy.git_reflog_entry_committer(entryHandle); + _committer = Proxy.git_reflog_entry_committer(entryHandle); message = Proxy.git_reflog_entry_message(entryHandle); } @@ -49,11 +50,11 @@ public virtual ObjectId To } /// - /// of the commiter of this reference update + /// of the committer of this reference update /// - public virtual Signature Commiter + public virtual Signature Committer { - get { return _commiter; } + get { return _committer; } } /// diff --git a/LibGit2Sharp/Remote.cs b/LibGit2Sharp/Remote.cs index dfff15fed..401a7ddd0 100644 --- a/LibGit2Sharp/Remote.cs +++ b/LibGit2Sharp/Remote.cs @@ -1,8 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Diagnostics; using System.Globalization; +using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -12,51 +12,96 @@ namespace LibGit2Sharp /// A remote repository whose branches are tracked. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Remote : IEquatable, IBelongToARepository + public class Remote : IBelongToARepository, IDisposable { - private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.Name, x => x.Url); - internal readonly Repository repository; private readonly RefSpecCollection refSpecs; + readonly RemoteHandle handle; + /// /// Needed for mocking purposes. /// protected Remote() { } - private Remote(RemoteSafeHandle handle, Repository repository) + internal Remote(RemoteHandle handle, Repository repository) { this.repository = repository; - Name = Proxy.git_remote_name(handle); - Url = Proxy.git_remote_url(handle); - TagFetchMode = Proxy.git_remote_autotag(handle); - refSpecs = new RefSpecCollection(handle); + this.handle = handle; + refSpecs = new RefSpecCollection(this, handle); + repository.RegisterForCleanup(this); + } + + /// + /// The finalizer for the class. + /// + ~Remote() + { + Dispose(false); + } + + #region IDisposable + + bool disposedValue = false; // To detect redundant calls + + /// + /// Release the unmanaged remote object + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } - internal static Remote BuildFromPtr(RemoteSafeHandle handle, Repository repo) + void Dispose(bool disposing) { - var remote = new Remote(handle, repo); + if (!disposedValue) + { + if (handle != null) + { + handle.Dispose(); + } - return remote; + disposedValue = true; + } } + #endregion + /// /// Gets the alias of this remote repository. /// - public virtual string Name { get; private set; } + public virtual string Name + { + get { return Proxy.git_remote_name(handle); } + } /// /// Gets the url to use to communicate with this remote repository. /// - public virtual string Url { get; private set; } + public virtual string Url + { + get { return Proxy.git_remote_url(handle); } + } + + /// + /// Gets the distinct push url for this remote repository, if set. + /// Defaults to the fetch url () if not set. + /// + public virtual string PushUrl + { + get { return Proxy.git_remote_pushurl(handle) ?? Url; } + } /// /// Gets the Tag Fetch Mode of the remote - indicating how tags are fetched. /// - public virtual TagFetchMode TagFetchMode { get; private set; } + public virtual TagFetchMode TagFetchMode + { + get { return Proxy.git_remote_autotag(handle); } + } /// /// Gets the list of s defined for this @@ -86,12 +131,12 @@ public virtual IEnumerable PushRefSpecs /// /// The reference to transform. /// The transformed reference. - internal string FetchSpecTransformToSource(string reference) + internal unsafe string FetchSpecTransformToSource(string reference) { - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, Name, true)) + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, Name, true)) { - GitRefSpecHandle fetchSpecPtr = Proxy.git_remote_get_refspec(remoteHandle, 0); - return Proxy.git_refspec_rtransform(fetchSpecPtr, reference); + git_refspec* fetchSpecPtr = Proxy.git_remote_get_refspec(remoteHandle, 0); + return Proxy.git_refspec_rtransform(new IntPtr(fetchSpecPtr), reference); } } @@ -106,72 +151,35 @@ public static bool IsValidName(string name) } /// - /// Determines if the proposed remote URL is supported by the library. - /// - /// The URL to be checked. - /// true if the url is supported; false otherwise. - public static bool IsSupportedUrl(string url) - { - return Proxy.git_remote_supported_url(url); - } - - /// - /// Determines whether the specified is equal to the current . + /// Gets the configured behavior regarding the deletion + /// of stale remote tracking branches. + /// + /// If defined, will return the value of the remote.<name>.prune entry. + /// Otherwise return the value of fetch.prune. + /// /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. - public override bool Equals(object obj) + public virtual bool AutomaticallyPruneOnFetch { - return Equals(obj as Remote); - } - - /// - /// Determines whether the specified is equal to the current . - /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. - public bool Equals(Remote other) - { - return equalityHelper.Equals(this, other); - } + get + { + var remotePrune = repository.Config.Get("remote", Name, "prune"); - /// - /// Returns the hash code for this instance. - /// - /// A 32-bit signed integer hash code. - public override int GetHashCode() - { - return equalityHelper.GetHashCode(this); - } + if (remotePrune != null) + { + return remotePrune.Value; + } - /// - /// Tests if two are equal. - /// - /// First to compare. - /// Second to compare. - /// True if the two objects are equal; false otherwise. - public static bool operator ==(Remote left, Remote right) - { - return Equals(left, right); - } + var fetchPrune = repository.Config.Get("fetch.prune"); - /// - /// Tests if two are different. - /// - /// First to compare. - /// Second to compare. - /// True if the two objects are different; false otherwise. - public static bool operator !=(Remote left, Remote right) - { - return !Equals(left, right); + return fetchPrune != null && fetchPrune.Value; + } } private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} => {1}", Name, Url); + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", Name, Url); } } diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 8adb17b4c..6061b10e1 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -12,37 +12,38 @@ namespace LibGit2Sharp /// internal class RemoteCallbacks { - internal RemoteCallbacks( - ProgressHandler onProgress = null, - TransferProgressHandler onDownloadProgress = null, - UpdateTipsHandler onUpdateTips = null, - ICredentialsProvider credentialsProvider = null) + internal RemoteCallbacks(CredentialsHandler credentialsProvider) { - Progress = onProgress; - DownloadTransferProgress = onDownloadProgress; - UpdateTips = onUpdateTips; - CredentialsProvider = credentialsProvider.GetCredentialsHandler(); + CredentialsProvider = credentialsProvider; } - internal RemoteCallbacks( - ProgressHandler onProgress = null, - TransferProgressHandler onDownloadProgress = null, - UpdateTipsHandler onUpdateTips = null, - CredentialsHandler credentialsProvider = null) + internal RemoteCallbacks(PushOptions pushOptions) { - Progress = onProgress; - DownloadTransferProgress = onDownloadProgress; - UpdateTips = onUpdateTips; - CredentialsProvider = credentialsProvider; + if (pushOptions == null) + { + return; + } + + PushTransferProgress = pushOptions.OnPushTransferProgress; + PackBuilderProgress = pushOptions.OnPackBuilderProgress; + CredentialsProvider = pushOptions.CredentialsProvider; + CertificateCheck = pushOptions.CertificateCheck; + PushStatusError = pushOptions.OnPushStatusError; + PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush; } - internal RemoteCallbacks(FetchOptions fetchOptions) + internal RemoteCallbacks(FetchOptionsBase fetchOptions) { - Ensure.ArgumentNotNull(fetchOptions, "fetchOptions"); + if (fetchOptions == null) + { + return; + } + Progress = fetchOptions.OnProgress; DownloadTransferProgress = fetchOptions.OnTransferProgress; UpdateTips = fetchOptions.OnUpdateTips; - CredentialsProvider = fetchOptions.GetCredentialsHandler(); + CredentialsProvider = fetchOptions.CredentialsProvider; + CertificateCheck = fetchOptions.CertificateCheck; } #region Delegates @@ -57,12 +58,33 @@ internal RemoteCallbacks(FetchOptions fetchOptions) /// private readonly UpdateTipsHandler UpdateTips; + /// + /// PushStatusError callback. It will be called when the libgit2 push_update_reference returns a non null status message, + /// which means that the update was rejected by the remote server. + /// + private readonly PushStatusErrorHandler PushStatusError; + /// /// Managed delegate to be called in response to a git_transfer_progress_callback callback from libgit2. /// This will in turn call the user provided delegate. /// private readonly TransferProgressHandler DownloadTransferProgress; + /// + /// Push transfer progress callback. + /// + private readonly PushTransferProgressHandler PushTransferProgress; + + /// + /// Pack builder creation progress callback. + /// + private readonly PackBuilderProgressHandler PackBuilderProgress; + + /// + /// Called during remote push operation after negotiation, before upload + /// + private readonly PrePushHandler PrePushCallback; + #endregion /// @@ -70,9 +92,14 @@ internal RemoteCallbacks(FetchOptions fetchOptions) /// private readonly CredentialsHandler CredentialsProvider; - internal GitRemoteCallbacks GenerateCallbacks() + /// + /// Callback to perform validation on the certificate + /// + private readonly CertificateCheckHandler CertificateCheck; + + internal unsafe GitRemoteCallbacks GenerateCallbacks() { - var callbacks = new GitRemoteCallbacks {version = 1}; + var callbacks = new GitRemoteCallbacks { version = 1 }; if (Progress != null) { @@ -84,16 +111,41 @@ internal GitRemoteCallbacks GenerateCallbacks() callbacks.update_tips = GitUpdateTipsHandler; } + if (PushStatusError != null) + { + callbacks.push_update_reference = GitPushUpdateReference; + } + if (CredentialsProvider != null) { callbacks.acquire_credentials = GitCredentialHandler; } + if (CertificateCheck != null) + { + callbacks.certificate_check = GitCertificateCheck; + } + if (DownloadTransferProgress != null) { callbacks.download_progress = GitDownloadTransferProgressHandler; } + if (PushTransferProgress != null) + { + callbacks.push_transfer_progress = GitPushTransferProgressHandler; + } + + if (PackBuilderProgress != null) + { + callbacks.pack_progress = GitPackbuilderProgressHandler; + } + + if (PrePushCallback != null) + { + callbacks.push_negotiation = GitPushNegotiationHandler; + } + return callbacks; } @@ -147,6 +199,30 @@ private int GitUpdateTipsHandler(IntPtr str, ref GitOid oldId, ref GitOid newId, return Proxy.ConvertResultToCancelFlag(shouldContinue); } + /// + /// The delegate with the signature that matches the native push_update_reference function's signature + /// + /// IntPtr to string, the name of the reference + /// IntPtr to string, the update status message + /// IntPtr to optional payload passed back to the callback. + /// 0 on success; a negative value to abort the process. + private int GitPushUpdateReference(IntPtr str, IntPtr status, IntPtr data) + { + PushStatusErrorHandler onPushError = PushStatusError; + + if (onPushError != null) + { + string reference = LaxUtf8Marshaler.FromNative(str); + string message = LaxUtf8Marshaler.FromNative(status); + if (message != null) + { + onPushError(new PushStatusError(reference, message)); + } + } + + return Proxy.ConvertResultToCancelFlag(true); + } + /// /// The delegate with the signature that matches the native git_transfer_progress_callback function's signature. /// @@ -165,7 +241,36 @@ private int GitDownloadTransferProgressHandler(ref GitTransferProgress progress, return Proxy.ConvertResultToCancelFlag(shouldContinue); } - private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFromUrl, GitCredentialType credTypes, IntPtr payload) + private int GitPushTransferProgressHandler(uint current, uint total, UIntPtr bytes, IntPtr payload) + { + bool shouldContinue = true; + + if (PushTransferProgress != null) + { + shouldContinue = PushTransferProgress((int)current, (int)total, (long)bytes); + } + + return Proxy.ConvertResultToCancelFlag(shouldContinue); + } + + private int GitPackbuilderProgressHandler(int stage, uint current, uint total, IntPtr payload) + { + bool shouldContinue = true; + + if (PackBuilderProgress != null) + { + shouldContinue = PackBuilderProgress((PackBuilderStage)stage, (int)current, (int)total); + } + + return Proxy.ConvertResultToCancelFlag(shouldContinue); + } + + private int GitCredentialHandler( + out IntPtr ptr, + IntPtr cUrl, + IntPtr usernameFromUrl, + GitCredentialType credTypes, + IntPtr payload) { string url = LaxUtf8Marshaler.FromNative(cUrl); string username = LaxUtf8Marshaler.FromNative(usernameFromUrl); @@ -180,9 +285,91 @@ private int GitCredentialHandler(out IntPtr ptr, IntPtr cUrl, IntPtr usernameFro types |= SupportedCredentialTypes.Default; } - var cred = CredentialsProvider(url, username, types); + ptr = IntPtr.Zero; + try + { + var cred = CredentialsProvider(url, username, types); + if (cred == null) + { + return (int)GitErrorCode.PassThrough; + } + return cred.GitCredentialHandler(out ptr); + } + catch (Exception exception) + { + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + return (int)GitErrorCode.Error; + } + } + + private unsafe int GitCertificateCheck(git_certificate* certPtr, int valid, IntPtr cHostname, IntPtr payload) + { + string hostname = LaxUtf8Marshaler.FromNative(cHostname); + Certificate cert = null; + + switch (certPtr->type) + { + case GitCertificateType.X509: + cert = new CertificateX509((git_certificate_x509*)certPtr); + break; + case GitCertificateType.Hostkey: + cert = new CertificateSsh((git_certificate_ssh*)certPtr); + break; + } + + bool result = false; + try + { + result = CertificateCheck(cert, valid != 0, hostname); + } + catch (Exception exception) + { + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + } + + return Proxy.ConvertResultToCancelFlag(result); + } + + private int GitPushNegotiationHandler(IntPtr updates, UIntPtr len, IntPtr payload) + { + if (updates == IntPtr.Zero) + { + return (int)GitErrorCode.Error; + } + + bool result = false; + try + { + + int length = len.ConvertToInt(); + PushUpdate[] pushUpdates = new PushUpdate[length]; + + unsafe + { + IntPtr* ptr = (IntPtr*)updates.ToPointer(); + + for (int i = 0; i < length; i++) + { + if (ptr[i] == IntPtr.Zero) + { + throw new NullReferenceException("Unexpected null git_push_update pointer was encountered"); + } + + PushUpdate pushUpdate = new PushUpdate((git_push_update*)ptr[i].ToPointer()); + pushUpdates[i] = pushUpdate; + } + + result = PrePushCallback(pushUpdates); + } + } + catch (Exception exception) + { + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + result = false; + } - return cred.GitCredentialHandler(out ptr, cUrl, usernameFromUrl, credTypes, payload); + return Proxy.ConvertResultToCancelFlag(result); } #endregion diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index 491e2d874..45e71c8b2 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -43,29 +43,28 @@ internal Remote RemoteForName(string name, bool shouldThrowIfNotFound = true) { Ensure.ArgumentNotNull(name, "name"); - using (RemoteSafeHandle handle = Proxy.git_remote_load(repository.Handle, name, shouldThrowIfNotFound)) - { - return handle == null ? null : Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_lookup(repository.Handle, name, shouldThrowIfNotFound); + return handle == null ? null : new Remote(handle, this.repository); } /// /// Update properties of a remote. + /// + /// These updates will be performed as a bulk update at the end of the method. /// - /// The remote to update. + /// The name of the remote to update. /// Delegate to perform updates on the remote. - /// The updated remote. - public virtual Remote Update(Remote remote, params Action[] actions) + public virtual void Update(string remote, params Action[] actions) { - using (var updater = new RemoteUpdater(this.repository, remote)) + var updater = new RemoteUpdater(repository, remote); + + repository.Config.WithinTransaction(() => { foreach (Action action in actions) { action(updater); } - } - - return this[remote.Name]; + }); } /// @@ -103,10 +102,8 @@ public virtual Remote Add(string name, string url) Ensure.ArgumentNotNull(name, "name"); Ensure.ArgumentNotNull(url, "url"); - using (RemoteSafeHandle handle = Proxy.git_remote_create(repository.Handle, name, url)) - { - return Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_create(repository.Handle, name, url); + return new Remote(handle, this.repository); } /// @@ -122,10 +119,8 @@ public virtual Remote Add(string name, string url, string fetchRefSpec) Ensure.ArgumentNotNull(url, "url"); Ensure.ArgumentNotNull(fetchRefSpec, "fetchRefSpec"); - using (RemoteSafeHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec)) - { - return Remote.BuildFromPtr(handle, this.repository); - } + RemoteHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec); + return new Remote(handle, this.repository); } /// @@ -140,6 +135,17 @@ public virtual void Remove(string name) Proxy.git_remote_delete(repository.Handle, name); } + /// + /// Renames an existing . + /// + /// The current remote name. + /// The new name the existing remote should bear. + /// A new . + public virtual Remote Rename(string name, string newName) + { + return Rename(name, newName, null); + } + /// /// Renames an existing . /// @@ -147,7 +153,7 @@ public virtual void Remove(string name) /// The new name the existing remote should bear. /// The callback to be used when problems with renaming occur. (e.g. non-default fetch refspecs) /// A new . - public virtual Remote Rename(string name, string newName, RemoteRenameFailureHandler callback = null) + public virtual Remote Rename(string name, string newName, RemoteRenameFailureHandler callback) { Ensure.ArgumentNotNull(name, "name"); Ensure.ArgumentNotNull(newName, "newName"); @@ -160,8 +166,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/RemoteRedirectMode.cs b/LibGit2Sharp/RemoteRedirectMode.cs new file mode 100644 index 000000000..029208857 --- /dev/null +++ b/LibGit2Sharp/RemoteRedirectMode.cs @@ -0,0 +1,28 @@ +namespace LibGit2Sharp +{ + /// + /// Remote redirection settings; whether redirects to another + /// host are permitted. By default, git will follow a redirect + /// on the initial request (`/info/refs`) but not subsequent + /// requests. + /// + public enum RemoteRedirectMode + { + /// + /// Do not follow any off-site redirects at any stage of + /// the fetch or push. + /// + None = 1 << 0, // GIT_REMOTE_REDIRECT_NONE + + /// + /// Allow off-site redirects only upon the initial + /// request. This is the default. + /// + Initial = 1 << 1, // GIT_REMOTE_REDIRECT_INITIAL + + /// + /// Allow redirects at any stage in the fetch or push. + /// + All = 1 << 2 // GIT_REMOTE_REDIRECT_ALL + } +} diff --git a/LibGit2Sharp/RemoteUpdater.cs b/LibGit2Sharp/RemoteUpdater.cs index 0ad7559ea..53fd33a4b 100644 --- a/LibGit2Sharp/RemoteUpdater.cs +++ b/LibGit2Sharp/RemoteUpdater.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -8,11 +9,12 @@ namespace LibGit2Sharp /// /// Exposes properties of a remote that can be updated. /// - public class RemoteUpdater : IDisposable + public class RemoteUpdater { private readonly UpdatingCollection fetchRefSpecs; private readonly UpdatingCollection pushRefSpecs; - private readonly RemoteSafeHandle remoteHandle; + private readonly Repository repo; + private readonly string remoteName; /// /// Needed for mocking purposes. @@ -25,32 +27,59 @@ internal RemoteUpdater(Repository repo, Remote remote) Ensure.ArgumentNotNull(repo, "repo"); Ensure.ArgumentNotNull(remote, "remote"); + this.repo = repo; + this.remoteName = remote.Name; + fetchRefSpecs = new UpdatingCollection(GetFetchRefSpecs, SetFetchRefSpecs); pushRefSpecs = new UpdatingCollection(GetPushRefSpecs, SetPushRefSpecs); + } - remoteHandle = Proxy.git_remote_load(repo.Handle, remote.Name, true); + internal RemoteUpdater(Repository repo, string remote) + { + Ensure.ArgumentNotNull(repo, "repo"); + Ensure.ArgumentNotNull(remote, "remote"); + + this.repo = repo; + this.remoteName = remote; + + fetchRefSpecs = new UpdatingCollection(GetFetchRefSpecs, SetFetchRefSpecs); + pushRefSpecs = new UpdatingCollection(GetPushRefSpecs, SetPushRefSpecs); } private IEnumerable GetFetchRefSpecs() { - return Proxy.git_remote_get_fetch_refspecs(remoteHandle); + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) + { + return Proxy.git_remote_get_fetch_refspecs(remoteHandle); + } } private void SetFetchRefSpecs(IEnumerable value) { - Proxy.git_remote_set_fetch_refspecs(remoteHandle, value); - Proxy.git_remote_save(remoteHandle); + repo.Config.UnsetAll(string.Format("remote.{0}.fetch", remoteName), ConfigurationLevel.Local); + + foreach (var url in value) + { + Proxy.git_remote_add_fetch(repo.Handle, remoteName, url); + } } private IEnumerable GetPushRefSpecs() { - return Proxy.git_remote_get_push_refspecs(remoteHandle); + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) + { + return Proxy.git_remote_get_push_refspecs(remoteHandle); + } } private void SetPushRefSpecs(IEnumerable value) { - Proxy.git_remote_set_push_refspecs(remoteHandle, value); - Proxy.git_remote_save(remoteHandle); + repo.Config.UnsetAll(string.Format("remote.{0}.push", remoteName), ConfigurationLevel.Local); + + foreach (var url in value) + { + Proxy.git_remote_add_push(repo.Handle, remoteName, url); + } } /// @@ -58,11 +87,7 @@ private void SetPushRefSpecs(IEnumerable value) /// public virtual TagFetchMode TagFetchMode { - set - { - Proxy.git_remote_set_autotag(remoteHandle, value); - Proxy.git_remote_save(remoteHandle); - } + set { Proxy.git_remote_set_autotag(repo.Handle, remoteName, value); } } /// @@ -70,11 +95,15 @@ public virtual TagFetchMode TagFetchMode /// public virtual string Url { - set - { - Proxy.git_remote_set_url(remoteHandle, value); - Proxy.git_remote_save(remoteHandle); - } + set { Proxy.git_remote_set_url(repo.Handle, remoteName, value); } + } + + /// + /// Sets the push url defined for this + /// + public virtual string PushUrl + { + set { Proxy.git_remote_set_pushurl(repo.Handle, remoteName, value); } } /// @@ -157,7 +186,7 @@ public IEnumerator GetEnumerator() return list.Value.GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { return list.Value.GetEnumerator(); } @@ -175,13 +204,5 @@ private void Save() setter(list.Value); } } - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - public void Dispose() - { - remoteHandle.Dispose(); - } } } diff --git a/LibGit2Sharp/RemoveFromIndexException.cs b/LibGit2Sharp/RemoveFromIndexException.cs index 0a60b6db8..847e4026e 100644 --- a/LibGit2Sharp/RemoveFromIndexException.cs +++ b/LibGit2Sharp/RemoveFromIndexException.cs @@ -1,21 +1,23 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; -using LibGit2Sharp.Core; +#endif namespace LibGit2Sharp { /// /// The exception that is thrown when a file cannot be removed from the index. /// +#if NETFRAMEWORK [Serializable] +#endif public class RemoveFromIndexException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public RemoveFromIndexException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,6 +25,15 @@ public RemoveFromIndexException() /// A message that describes the error. public RemoveFromIndexException(string message) : base(message) + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public RemoveFromIndexException(string format, params object[] args) + : base(format, args) { } @@ -33,9 +44,9 @@ public RemoveFromIndexException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public RemoveFromIndexException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +54,7 @@ public RemoveFromIndexException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected RemoveFromIndexException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/RenameDetails.cs b/LibGit2Sharp/RenameDetails.cs index 199b7269f..8742ff0d3 100644 --- a/LibGit2Sharp/RenameDetails.cs +++ b/LibGit2Sharp/RenameDetails.cs @@ -56,10 +56,10 @@ public virtual int Similarity } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as RenameDetails); @@ -110,9 +110,11 @@ private string DebuggerDisplay { get { - return string.Format( - CultureInfo.InvariantCulture, - "{0} -> {1} [{2}%]", OldFilePath, NewFilePath, Similarity); + return string.Format(CultureInfo.InvariantCulture, + "{0} -> {1} [{2}%]", + OldFilePath, + NewFilePath, + Similarity); } } } diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 64c740778..9ac5e2424 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -4,7 +4,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Text.RegularExpressions; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -22,8 +21,8 @@ public sealed class Repository : IRepository private readonly BranchCollection branches; private readonly CommitLog commits; private readonly Lazy config; - private readonly RepositorySafeHandle handle; - private readonly Index index; + private readonly RepositoryHandle handle; + private readonly Lazy index; private readonly ReferenceCollection refs; private readonly TagCollection tags; private readonly StashCollection stashes; @@ -32,30 +31,75 @@ public sealed class Repository : IRepository private readonly NoteCollection notes; private readonly Lazy odb; private readonly Lazy network; + private readonly Lazy rebaseOperation; private readonly Stack toCleanup = new Stack(); private readonly Ignore ignore; private readonly SubmoduleCollection submodules; + private readonly WorktreeCollection worktrees; private readonly Lazy pathCase; + [Flags] + private enum RepositoryRequiredParameter + { + None = 0, + Path = 1, + Options = 2, + } + /// - /// Initializes a new instance of the class, providing ooptional behavioral overrides through parameter. + /// Initializes a new instance of the class + /// that does not point to an on-disk Git repository. This is + /// suitable only for custom, in-memory Git repositories that are + /// configured with custom object database, reference database and/or + /// configuration backends. + /// + public Repository() + : this(null, null, RepositoryRequiredParameter.None) + { + } + + /// + /// Initializes a new instance of the class. /// For a standard repository, should either point to the ".git" folder or to the working directory. For a bare repository, should directly point to the repository folder. /// /// /// The path to the git repository to open, can be either the path to the git directory (for non-bare repositories this /// would be the ".git" folder inside the working directory) or the path to the working directory. /// + public Repository(string path) + : this(path, null, RepositoryRequiredParameter.Path) + { } + + /// + /// Initializes a new instance of the class, + /// providing optional behavioral overrides through the + /// parameter. + /// For a standard repository, may + /// either point to the ".git" folder or to the working directory. + /// For a bare repository, should directly + /// point to the repository folder. + /// + /// + /// The path to the git repository to open, can be either the + /// path to the git directory (for non-bare repositories this + /// would be the ".git" folder inside the working directory) + /// or the path to the working directory. + /// /// /// Overrides to the way a repository is opened. /// - public Repository(string path, RepositoryOptions options = null) + public Repository(string path, RepositoryOptions options) : + this(path, options, RepositoryRequiredParameter.Path | RepositoryRequiredParameter.Options) { - Ensure.ArgumentNotNullOrEmptyString(path, "path"); + } + internal Repository(WorktreeHandle worktreeHandle) + { try { - handle = Proxy.git_repository_open(path); + handle = Proxy.git_repository_open_from_worktree(worktreeHandle); RegisterForCleanup(handle); + RegisterForCleanup(worktreeHandle); isBare = Proxy.git_repository_is_bare(handle); @@ -65,6 +109,72 @@ public Repository(string path, RepositoryOptions options = null) string configurationXDGFilePath = null; string configurationSystemFilePath = null; + if (!isBare) + { + index = new Lazy(() => indexBuilder()); + } + + commits = new CommitLog(this); + refs = new ReferenceCollection(this); + branches = new BranchCollection(this); + tags = new TagCollection(this); + stashes = new StashCollection(this); + info = new Lazy(() => new RepositoryInformation(this, isBare)); + config = new Lazy(() => RegisterForCleanup(new Configuration(this, + null, + configurationGlobalFilePath, + configurationXDGFilePath, + configurationSystemFilePath))); + odb = new Lazy(() => new ObjectDatabase(this)); + diff = new Diff(this); + notes = new NoteCollection(this); + ignore = new Ignore(this); + network = new Lazy(() => new Network(this)); + rebaseOperation = new Lazy(() => new Rebase(this)); + pathCase = new Lazy(() => new PathCase(this)); + submodules = new SubmoduleCollection(this); + worktrees = new WorktreeCollection(this); + } + catch + { + CleanupDisposableDependencies(); + throw; + } + } + + private Repository(string path, RepositoryOptions options, RepositoryRequiredParameter requiredParameter) + { + if ((requiredParameter & RepositoryRequiredParameter.Path) == RepositoryRequiredParameter.Path) + { + Ensure.ArgumentNotNullOrEmptyString(path, "path"); + } + + if ((requiredParameter & RepositoryRequiredParameter.Options) == RepositoryRequiredParameter.Options) + { + Ensure.ArgumentNotNull(options, "options"); + } + + try + { + handle = (path != null) ? Proxy.git_repository_open(path) : Proxy.git_repository_new(); + RegisterForCleanup(handle); + + isBare = Proxy.git_repository_is_bare(handle); + + /* TODO: bug in libgit2, update when fixed by + * https://github.com/libgit2/libgit2/pull/2970 + */ + if (path == null) + { + isBare = true; + } + + Func indexBuilder = () => new Index(this); + + string configurationGlobalFilePath = null; + string configurationXDGFilePath = null; + string configurationSystemFilePath = null; + if (options != null) { bool isWorkDirNull = string.IsNullOrEmpty(options.WorkingDirectoryPath); @@ -72,11 +182,13 @@ public Repository(string path, RepositoryOptions options = null) if (isBare && (isWorkDirNull ^ isIndexNull)) { - throw new ArgumentException( - "When overriding the opening of a bare repository, both RepositoryOptions.WorkingDirectoryPath an RepositoryOptions.IndexPath have to be provided."); + throw new ArgumentException("When overriding the opening of a bare repository, both RepositoryOptions.WorkingDirectoryPath an RepositoryOptions.IndexPath have to be provided."); } - isBare = false; + if (!isWorkDirNull) + { + isBare = false; + } if (!isIndexNull) { @@ -88,14 +200,15 @@ public Repository(string path, RepositoryOptions options = null) Proxy.git_repository_set_workdir(handle, options.WorkingDirectoryPath); } - configurationGlobalFilePath = options.GlobalConfigurationLocation; - configurationXDGFilePath = options.XdgConfigurationLocation; - configurationSystemFilePath = options.SystemConfigurationLocation; + if (options.Identity != null) + { + Proxy.git_repository_set_ident(handle, options.Identity.Name, options.Identity.Email); + } } if (!isBare) { - index = indexBuilder(); + index = new Lazy(() => indexBuilder()); } commits = new CommitLog(this); @@ -104,20 +217,22 @@ public Repository(string path, RepositoryOptions options = null) tags = new TagCollection(this); stashes = new StashCollection(this); info = new Lazy(() => new RepositoryInformation(this, isBare)); - config = - new Lazy( - () => - RegisterForCleanup(new Configuration(this, configurationGlobalFilePath, configurationXDGFilePath, - configurationSystemFilePath))); + config = new Lazy(() => RegisterForCleanup(new Configuration(this, + null, + configurationGlobalFilePath, + configurationXDGFilePath, + configurationSystemFilePath))); odb = new Lazy(() => new ObjectDatabase(this)); diff = new Diff(this); notes = new NoteCollection(this); ignore = new Ignore(this); network = new Lazy(() => new Network(this)); + rebaseOperation = new Lazy(() => new Rebase(this)); pathCase = new Lazy(() => new PathCase(this)); submodules = new SubmoduleCollection(this); + worktrees = new WorktreeCollection(this); - EagerlyLoadTheConfigIfAnyPathHaveBeenPassed(options); + EagerlyLoadComponentsWithSpecifiedPaths(options); } catch { @@ -136,7 +251,12 @@ public Repository(string path, RepositoryOptions options = null) /// True if a repository can be resolved through this path; false otherwise static public bool IsValid(string path) { - Ensure.ArgumentNotNullOrEmptyString(path, "path"); + Ensure.ArgumentNotNull(path, "path"); + + if (string.IsNullOrWhiteSpace(path)) + { + return false; + } try { @@ -150,30 +270,24 @@ static public bool IsValid(string path) return true; } - private void EagerlyLoadTheConfigIfAnyPathHaveBeenPassed(RepositoryOptions options) + private void EagerlyLoadComponentsWithSpecifiedPaths(RepositoryOptions options) { if (options == null) { return; } - if (options.GlobalConfigurationLocation == null && - options.XdgConfigurationLocation == null && - options.SystemConfigurationLocation == null) + if (!string.IsNullOrEmpty(options.IndexPath)) { - return; - } - - // Dirty hack to force the eager load of the configuration - // without Resharper pestering about useless code - - if (!Config.HasConfig(ConfigurationLevel.Local)) - { - throw new InvalidOperationException("Unexpected state."); + // Another dirty hack to avoid warnings + if (Index.Count < 0) + { + throw new InvalidOperationException("Unexpected state."); + } } } - internal RepositorySafeHandle Handle + internal RepositoryHandle Handle { get { return handle; } } @@ -221,7 +335,7 @@ public Index Index throw new BareRepositoryException("Index is not available in a bare repository."); } - return index; + return index != null ? index.Value : null; } } @@ -230,20 +344,25 @@ public Index Index /// public Ignore Ignore { - get - { - return ignore; - } + get { return ignore; } } /// /// Provides access to network functionality for a repository. /// public Network Network + { + get { return network.Value; } + } + + /// + /// Provides access to rebase functionality for a repository. + /// + public Rebase Rebase { get { - return network.Value; + return rebaseOperation.Value; } } @@ -252,10 +371,7 @@ public Network Network /// public ObjectDatabase ObjectDatabase { - get - { - return odb.Value; - } + get { return odb.Value; } } /// @@ -331,6 +447,14 @@ public SubmoduleCollection Submodules get { return submodules; } } + /// + /// Worktrees in the repository. + /// + public WorktreeCollection Worktrees + { + get { return worktrees; } + } + #region IDisposable Members /// @@ -351,17 +475,27 @@ private void Dispose(bool disposing) #endregion + /// + /// Initialize a repository at the specified . + /// + /// The path to the working folder when initializing a standard ".git" repository. Otherwise, when initializing a bare repository, the path to the expected location of this later. + /// The path to the created repository. + public static string Init(string path) + { + return Init(path, false); + } + /// /// Initialize a repository at the specified . /// /// The path to the working folder when initializing a standard ".git" repository. Otherwise, when initializing a bare repository, the path to the expected location of this later. /// true to initialize a bare repository. False otherwise, to initialize a standard ".git" repository. /// The path to the created repository. - public static string Init(string path, bool isBare = false) + public static string Init(string path, bool isBare) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - using (RepositorySafeHandle repo = Proxy.git_repository_init_ext(null, path, isBare)) + using (RepositoryHandle repo = Proxy.git_repository_init_ext(null, path, isBare)) { FilePath repoPath = Proxy.git_repository_path(repo); return repoPath.Native; @@ -386,7 +520,7 @@ public static string Init(string workingDirectoryPath, string gitDirectoryPath) // TODO: Shouldn't we ensure that the working folder isn't under the gitDir? - using (RepositorySafeHandle repo = Proxy.git_repository_init_ext(wd, gitDirectoryPath, false)) + using (RepositoryHandle repo = Proxy.git_repository_init_ext(wd, gitDirectoryPath, false)) { FilePath repoPath = Proxy.git_repository_path(repo); return repoPath.Native; @@ -435,13 +569,13 @@ public GitObject Lookup(string objectish, ObjectType type) return Lookup(objectish, type.ToGitObjectType(), LookUpOptions.None); } - internal GitObject LookupInternal(ObjectId id, GitObjectType type, FilePath knownPath) + internal GitObject LookupInternal(ObjectId id, GitObjectType type, string knownPath) { Ensure.ArgumentNotNull(id, "id"); - using (GitObjectSafeHandle obj = Proxy.git_object_lookup(handle, id, type)) + using (ObjectHandle obj = Proxy.git_object_lookup(handle, id, type)) { - if (obj == null) + if (obj == null || obj.IsInvalid) { return null; } @@ -471,7 +605,7 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo Ensure.ArgumentNotNullOrEmptyString(objectish, "objectish"); GitObject obj; - using (GitObjectSafeHandle sh = Proxy.git_revparse_single(handle, objectish)) + using (ObjectHandle sh = Proxy.git_revparse_single(handle, objectish)) { if (sh == null) { @@ -495,8 +629,7 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo if (lookUpOptions.HasFlag(LookUpOptions.DereferenceResultToCommit)) { - return obj.DereferenceToCommit( - lookUpOptions.HasFlag(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); + return obj.Peel(lookUpOptions.HasFlag(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); } return obj; @@ -504,10 +637,89 @@ internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lo internal Commit LookupCommit(string committish) { - return (Commit)Lookup(committish, GitObjectType.Any, - LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | - LookUpOptions.DereferenceResultToCommit | - LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + return (Commit)Lookup(committish, + GitObjectType.Any, + LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | + LookUpOptions.DereferenceResultToCommit | + LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + } + + /// + /// Lists the Remote Repository References. + /// + /// + /// Does not require a local Repository. The retrieved + /// + /// throws in this case. + /// + /// The url to list from. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url) + { + return ListRemoteReferences(url, null, new ProxyOptions()); + } + + /// + /// Lists the Remote Repository References. + /// + /// The url to list from. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, ProxyOptions proxyOptions) + { + return ListRemoteReferences(url, null, proxyOptions); + } + + /// + /// Lists the Remote Repository References. + /// + /// + /// Does not require a local Repository. The retrieved + /// + /// throws in this case. + /// + /// The url to list from. + /// The used to connect to remote repository. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, CredentialsHandler credentialsProvider) + { + return ListRemoteReferences(url, credentialsProvider, new ProxyOptions()); + } + + /// + /// Lists the Remote Repository References. + /// + /// + /// Does not require a local Repository. The retrieved + /// + /// throws in this case. + /// + /// The url to list from. + /// The used to connect to remote repository. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public static IEnumerable ListRemoteReferences(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + { + Ensure.ArgumentNotNull(url, "url"); + + proxyOptions ??= new(); + + using RepositoryHandle repositoryHandle = Proxy.git_repository_new(); + using RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repositoryHandle, url); + using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); + + var gitCallbacks = new GitRemoteCallbacks { version = 1 }; + + if (credentialsProvider != null) + { + var callbacks = new RemoteCallbacks(credentialsProvider); + gitCallbacks = callbacks.GenerateCallbacks(); + } + + var gitProxyOptions = proxyOptionsWrapper.Options; + + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref gitProxyOptions); + return Proxy.git_remote_ls(null, remoteHandle); } /// @@ -515,7 +727,7 @@ internal Commit LookupCommit(string committish) /// The lookup start from and walk upward parent directories if nothing has been found. /// /// The base path where the lookup starts. - /// The path to the git repository. + /// The path to the git repository, or null if no repository was found. public static string Discover(string startingPath) { FilePath discoveredPath = Proxy.git_repository_discover(startingPath); @@ -528,184 +740,229 @@ public static string Discover(string startingPath) return discoveredPath.Native; } + /// + /// Clone using default options. + /// + /// This exception is thrown when there + /// is an error is encountered while recursively cloning submodules. The inner exception + /// will contain the original exception. The initially cloned repository would + /// be reported through the + /// property." + /// Exception thrown when the cancelling + /// the clone of the initial repository." + /// URI for the remote repository + /// Local path to clone into + /// The path to the created repository. + public static string Clone(string sourceUrl, string workdirPath) + { + return Clone(sourceUrl, workdirPath, null); + } + /// /// Clone with specified options. /// + /// This exception is thrown when there + /// is an error is encountered while recursively cloning submodules. The inner exception + /// will contain the original exception. The initially cloned repository would + /// be reported through the + /// property." + /// Exception thrown when the cancelling + /// the clone of the initial repository." /// URI for the remote repository /// Local path to clone into /// controlling clone behavior /// The path to the created repository. - public static string Clone(string sourceUrl, string workdirPath, - CloneOptions options = null) + public static string Clone(string sourceUrl, string workdirPath, CloneOptions options) { - options = options ?? new CloneOptions(); + Ensure.ArgumentNotNull(sourceUrl, "sourceUrl"); + Ensure.ArgumentNotNull(workdirPath, "workdirPath"); - using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + options ??= new CloneOptions(); + + // context variable that contains information on the repository that + // we are cloning. + var context = new RepositoryOperationContext(Path.GetFullPath(workdirPath), sourceUrl); + + // Notify caller that we are starting to work with the current repository. + bool continueOperation = OnRepositoryOperationStarting(options.FetchOptions.RepositoryOperationStarting, context); + + if (!continueOperation) + { + throw new UserCancelledException("Clone cancelled by the user."); + } + + using (var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + using (var fetchOptionsWrapper = new GitFetchOptionsWrapper()) { var gitCheckoutOptions = checkoutOptionsWrapper.Options; - var remoteCallbacks = new RemoteCallbacks(null, options.OnTransferProgress, null, options.CredentialsProvider); - var gitRemoteCallbacks = remoteCallbacks.GenerateCallbacks(); + var gitFetchOptions = fetchOptionsWrapper.Options; + gitFetchOptions.Depth = options.FetchOptions.Depth; + gitFetchOptions.ProxyOptions = options.FetchOptions.ProxyOptions.CreateGitProxyOptions(); + gitFetchOptions.RemoteCallbacks = new RemoteCallbacks(options.FetchOptions).GenerateCallbacks(); + + if (options.FetchOptions != null && options.FetchOptions.CustomHeaders != null) + { + gitFetchOptions.CustomHeaders = GitStrArrayManaged.BuildFrom(options.FetchOptions.CustomHeaders); + } var cloneOpts = new GitCloneOptions { Version = 1, Bare = options.IsBare ? 1 : 0, CheckoutOpts = gitCheckoutOptions, - RemoteCallbacks = gitRemoteCallbacks, + FetchOpts = gitFetchOptions, }; - FilePath repoPath; - using (RepositorySafeHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) + string clonedRepoPath; + + try + { + cloneOpts.CheckoutBranch = StrictUtf8Marshaler.FromManaged(options.BranchName); + + using (RepositoryHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) + { + clonedRepoPath = Proxy.git_repository_path(repo).Native; + } + } + finally { - repoPath = Proxy.git_repository_path(repo); + EncodingMarshaler.Cleanup(cloneOpts.CheckoutBranch); } - return repoPath.Native; - } - } + // Notify caller that we are done with the current repository. + OnRepositoryOperationCompleted(options.FetchOptions.RepositoryOperationCompleted, context); - /// - /// Find where each line of a file originated. - /// - /// Path of the file to blame. - /// Specifies optional parameters; if null, the defaults are used. - /// The blame for the file. - public BlameHunkCollection Blame(string path, BlameOptions options) - { - return new BlameHunkCollection(this, Handle, path, options ?? new BlameOptions()); + // Recursively clone submodules if requested. + try + { + RecursivelyCloneSubmodules(options, clonedRepoPath, 1); + } + catch (Exception ex) + { + throw new RecurseSubmodulesException("The top level repository was cloned, but there was an error cloning its submodules.", ex, clonedRepoPath); + } + + return clonedRepoPath; + } } /// - /// Checkout the specified , reference or SHA. - /// - /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will - /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// + /// Recursively clone submodules if directed to do so by the clone options. /// - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - public Branch Checkout(string committishOrBranchSpec, CheckoutOptions options, Signature signature) + /// Options controlling clone behavior. + /// Path of the parent repository. + /// The current depth of the recursion. + private static void RecursivelyCloneSubmodules(CloneOptions options, string repoPath, int recursionDepth) { - Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); - Ensure.ArgumentNotNull(options, "options"); - - var handles = Proxy.git_revparse_ext(Handle, committishOrBranchSpec); - if (handles == null) + if (options.RecurseSubmodules) { - Ensure.GitObjectIsNotNull(null, committishOrBranchSpec); - } + List submodules = new List(); - var objH = handles.Item1; - var refH = handles.Item2; - GitObject obj; - try - { - if (!refH.IsInvalid) + using (Repository repo = new Repository(repoPath)) { - var reference = Reference.BuildFromPtr(refH, this); - if (reference.IsLocalBranch()) + var updateOptions = new SubmoduleUpdateOptions() { - Branch branch = Branches[reference.CanonicalName]; - return Checkout(branch, options, signature); + Init = true, + OnCheckoutProgress = options.OnCheckoutProgress, + FetchOptions = options.FetchOptions + }; + + string parentRepoWorkDir = repo.Info.WorkingDirectory; + + // Iterate through the submodules (where the submodule is in the index), + // and clone them. + foreach (var sm in repo.Submodules.Where(sm => sm.RetrieveStatus().HasFlag(SubmoduleStatus.InIndex))) + { + string fullSubmodulePath = Path.Combine(parentRepoWorkDir, sm.Path); + + // Resolve the URL in the .gitmodule file to the one actually used + // to clone + string resolvedUrl = Proxy.git_submodule_resolve_url(repo.Handle, sm.Url); + + var context = new RepositoryOperationContext(fullSubmodulePath, + resolvedUrl, + parentRepoWorkDir, + sm.Name, + recursionDepth); + + bool continueOperation = OnRepositoryOperationStarting(options.FetchOptions.RepositoryOperationStarting, + context); + + if (!continueOperation) + { + throw new UserCancelledException("Recursive clone of submodules was cancelled."); + } + + repo.Submodules.Update(sm.Name, updateOptions); + + OnRepositoryOperationCompleted(options.FetchOptions.RepositoryOperationCompleted, + context); + + submodules.Add(Path.Combine(repo.Info.WorkingDirectory, sm.Path)); } } - obj = GitObject.BuildFrom(this, Proxy.git_object_id(objH), Proxy.git_object_type(objH), - PathFromRevparseSpec(committishOrBranchSpec)); - } - finally - { - objH.Dispose(); - refH.Dispose(); + // If we are continuing the recursive operation, then + // recurse into nested submodules. + // Check submodules to see if they have their own submodules. + foreach (string submodule in submodules) + { + RecursivelyCloneSubmodules(options, submodule, recursionDepth + 1); + } } - - Commit commit = obj.DereferenceToCommit(true); - Checkout(commit.Tree, options, commit.Id.Sha, committishOrBranchSpec, signature); - - return Head; } /// - /// Checkout the tip commit of the specified object. If this commit is the - /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit - /// as a detached HEAD. + /// If a callback has been provided to notify callers that we are + /// either starting to work on a repository. /// - /// The to check out. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - public Branch Checkout(Branch branch, CheckoutOptions options, Signature signature) + /// The callback to notify change. + /// Context of the repository this operation affects. + /// true to continue the operation, false to cancel. + private static bool OnRepositoryOperationStarting( + RepositoryOperationStarting repositoryChangedCallback, + RepositoryOperationContext context) { - Ensure.ArgumentNotNull(branch, "branch"); - Ensure.ArgumentNotNull(options, "options"); - - // Make sure this is not an unborn branch. - if (branch.Tip == null) + bool continueOperation = true; + if (repositoryChangedCallback != null) { - throw new UnbornBranchException( - string.Format(CultureInfo.InvariantCulture, - "The tip of branch '{0}' is null. There's nothing to checkout.", branch.Name)); + continueOperation = repositoryChangedCallback(context); } - if (!branch.IsRemote && !(branch is DetachedHead) && - string.Equals(Refs[branch.CanonicalName].TargetIdentifier, branch.Tip.Id.Sha, - StringComparison.OrdinalIgnoreCase)) - { - Checkout(branch.Tip.Tree, options, branch.CanonicalName, branch.Name, signature); - } - else + return continueOperation; + } + + private static void OnRepositoryOperationCompleted( + RepositoryOperationCompleted repositoryChangedCallback, + RepositoryOperationContext context) + { + if (repositoryChangedCallback != null) { - Checkout(branch.Tip.Tree, options, branch.Tip.Id.Sha, branch.Name, signature); + repositoryChangedCallback(context); } - - return Head; } /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// + /// Find where each line of a file originated. /// - /// The to check out. - /// controlling checkout behavior. - /// Identity for use when updating the reflog. - /// The that was checked out. - public Branch Checkout(Commit commit, CheckoutOptions options, Signature signature) + /// Path of the file to blame. + /// Specifies optional parameters; if null, the defaults are used. + /// The blame for the file. + public BlameHunkCollection Blame(string path, BlameOptions options) { - Ensure.ArgumentNotNull(commit, "commit"); - Ensure.ArgumentNotNull(options, "options"); - - Checkout(commit.Tree, options, commit.Id.Sha, commit.Id.Sha, signature); - - return Head; + return new BlameHunkCollection(this, Handle, path, options ?? new BlameOptions()); } /// - /// Internal implementation of Checkout that expects the ID of the checkout target - /// to already be in the form of a canonical branch name or a commit ID. + /// Checkout the specified tree. /// /// The to checkout. - /// controlling checkout behavior. - /// Target for the new HEAD. - /// The spec which will be written as target in the reflog. - /// Identity for use when updating the reflog. - private void Checkout( - Tree tree, - CheckoutOptions checkoutOptions, - string headTarget, string refLogHeadSpec, Signature signature) + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + public void Checkout(Tree tree, IEnumerable paths, CheckoutOptions options) { - var previousHeadName = Info.IsHeadDetached ? Head.Tip.Sha : Head.Name; - - CheckoutTree(tree, null, checkoutOptions); - - Refs.UpdateTarget("HEAD", headTarget, signature, - string.Format( - CultureInfo.InvariantCulture, - "checkout: moving from {0} to {1}", previousHeadName, refLogHeadSpec)); + CheckoutTree(tree, paths != null ? paths.ToList() : null, options); } /// @@ -714,13 +971,10 @@ private void Checkout( /// The to checkout. /// The paths to checkout. /// Collection of parameters controlling checkout behavior. - private void CheckoutTree( - Tree tree, - IList paths, - IConvertableToGitCheckoutOpts opts) + private void CheckoutTree(Tree tree, IList paths, IConvertableToGitCheckoutOpts opts) { - using(GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) { var options = checkoutOptionsWrapper.Options; Proxy.git_checkout_tree(Handle, tree.Id, ref options); @@ -733,37 +987,27 @@ private void CheckoutTree( /// /// Flavor of reset operation to perform. /// The target commit object. - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - public void Reset(ResetMode resetMode, Commit commit, Signature signature, string logMessage) + public void Reset(ResetMode resetMode, Commit commit) { - Reset(resetMode, commit, new CheckoutOptions(), signature, logMessage); + Reset(resetMode, commit, new CheckoutOptions()); } /// - /// Sets the current to the specified commit and optionally resets the and + /// Sets to the specified commit and optionally resets the and /// the content of the working tree to match. /// /// Flavor of reset operation to perform. /// The target commit object. /// Collection of parameters controlling checkout behavior. - /// Identity for use when updating the reflog. - /// Message to use when updating the reflog. - private void Reset(ResetMode resetMode, Commit commit, IConvertableToGitCheckoutOpts opts, Signature signature, string logMessage) + public void Reset(ResetMode resetMode, Commit commit, CheckoutOptions opts) { Ensure.ArgumentNotNull(commit, "commit"); - - if (logMessage == null) - { - logMessage = string.Format( - CultureInfo.InvariantCulture, - "reset: moving to {0}", commit.Sha); - } + Ensure.ArgumentNotNull(opts, "opts"); using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts)) { var options = checkoutOptionsWrapper.Options; - Proxy.git_reset(handle, commit.Id, resetMode, ref options, signature.OrDefault(Config), logMessage); + Proxy.git_reset(handle, commit.Id, resetMode, ref options); } } @@ -781,36 +1025,17 @@ public void CheckoutPaths(string committishOrBranchSpec, IEnumerable pat Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); Ensure.ArgumentNotNull(paths, "paths"); + var listOfPaths = paths.ToList(); + // If there are no paths, then there is nothing to do. - if (!paths.Any()) + if (listOfPaths.Count == 0) { return; } Commit commit = LookupCommit(committishOrBranchSpec); - CheckoutTree(commit.Tree, paths.ToList(), checkoutOptions ?? new CheckoutOptions()); - } - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public void Reset(Commit commit, IEnumerable paths, ExplicitPathsOptions explicitPathsOptions) - { - if (Info.IsBare) - { - throw new BareRepositoryException("Reset is not allowed in a bare repository"); - } - - Ensure.ArgumentNotNull(commit, "commit"); - - var changes = Diff.Compare(commit.Tree, DiffTargets.Index, paths, explicitPathsOptions, new CompareOptions { Similarity = SimilarityOptions.None }); - Index.Reset(changes); + CheckoutTree(commit.Tree, listOfPaths, checkoutOptions ?? new CheckoutOptions()); } /// @@ -837,7 +1062,7 @@ public Commit Commit(string message, Signature author, Signature committer, Comm throw new UnbornBranchException("Can not amend anything. The Head doesn't point at any commit."); } - var treeId = Proxy.git_tree_create_fromindex(Index); + var treeId = Proxy.git_index_write_tree(Index.Handle); var tree = this.Lookup(treeId); var parents = RetrieveParentsOfTheCommitBeingCreated(options.AmendPreviousCommit).ToList(); @@ -849,11 +1074,9 @@ public Commit Commit(string message, Signature author, Signature committer, Comm if (treesame && !amendMergeCommit) { - throw new EmptyCommitException( - options.AmendPreviousCommit ? - String.Format(CultureInfo.InvariantCulture, - "Amending this commit would produce a commit that is identical to its parent (id = {0})", parents[0].Id) : - "No changes; nothing to commit."); + throw (options.AmendPreviousCommit ? + new EmptyCommitException("Amending this commit would produce a commit that is identical to its parent (id = {0})", parents[0].Id) : + new EmptyCommitException("No changes; nothing to commit.")); } } @@ -894,17 +1117,17 @@ private void UpdateHeadAndTerminalReference(Commit commit, string reflogMessage) { if (reference is DirectReference) { - Refs.UpdateTarget(reference, commit.Id, commit.Committer, reflogMessage); + Refs.UpdateTarget(reference, commit.Id, reflogMessage); return; } - var symRef = (SymbolicReference) reference; + var symRef = (SymbolicReference)reference; reference = symRef.Target; if (reference == null) { - Refs.Add(symRef.TargetIdentifier, commit.Id, commit.Committer, reflogMessage); + Refs.Add(symRef.TargetIdentifier, commit.Id, reflogMessage); return; } } @@ -935,7 +1158,7 @@ private IEnumerable RetrieveParentsOfTheCommitBeingCreated(bool amendPre /// /// Clean the working tree by removing files that are not under version control. /// - public void RemoveUntrackedFiles() + public unsafe void RemoveUntrackedFiles() { var options = new GitCheckoutOpts { @@ -944,7 +1167,7 @@ public void RemoveUntrackedFiles() | CheckoutStrategy.GIT_CHECKOUT_ALLOW_CONFLICTS, }; - Proxy.git_checkout_index(Handle, new NullGitObjectSafeHandle(), ref options); + Proxy.git_checkout_index(Handle, new ObjectHandle(null, false), ref options); } private void CleanupDisposableDependencies() @@ -961,19 +1184,6 @@ internal T RegisterForCleanup(T disposable) where T : IDisposable return disposable; } - /// - /// Gets the current LibGit2Sharp version. - /// - /// The format of the version number is as follows: - /// Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features) - /// - /// - [Obsolete("This property will be removed in the next release. Use GlobalSettings.Version instead.")] - public static string Version - { - get { return GlobalSettings.Version.ToString(); } - } - /// /// Merges changes from commit into the branch pointed at by HEAD. /// @@ -988,9 +1198,9 @@ public MergeResult Merge(Commit commit, Signature merger, MergeOptions options) options = options ?? new MergeOptions(); - using (GitMergeHeadHandle mergeHeadHandle = Proxy.git_merge_head_from_id(Handle, commit.Id.Oid)) + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_lookup(Handle, commit.Id.Oid)) { - return Merge(new[] { mergeHeadHandle }, merger, options); + return Merge(new[] { annotatedCommitHandle }, merger, options); } } @@ -1008,10 +1218,10 @@ public MergeResult Merge(Branch branch, Signature merger, MergeOptions options) options = options ?? new MergeOptions(); - using (ReferenceSafeHandle referencePtr = Refs.RetrieveReferencePtr(branch.CanonicalName)) - using (GitMergeHeadHandle mergeHeadHandle = Proxy.git_merge_head_from_ref(Handle, referencePtr)) + using (ReferenceHandle referencePtr = Refs.RetrieveReferencePtr(branch.CanonicalName)) + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_from_ref(Handle, referencePtr)) { - return Merge(new[] { mergeHeadHandle }, merger, options); + return Merge(new[] { annotatedCommitHandle }, merger, options); } } @@ -1034,12 +1244,16 @@ public MergeResult Merge(string committish, Signature merger, MergeOptions optio } /// - /// Merge the current fetch heads into the branch pointed at by HEAD. + /// Merge the reference that was recently fetched. This will merge + /// the branch on the fetched remote that corresponded to the + /// current local branch when we did the fetch. This is the + /// second step in performing a pull operation (after having + /// performed said fetch). /// /// The of who is performing the merge. /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. /// The of the merge. - internal MergeResult MergeFetchHeads(Signature merger, MergeOptions options) + public MergeResult MergeFetchedRefs(Signature merger, MergeOptions options) { Ensure.ArgumentNotNull(merger, "merger"); @@ -1051,24 +1265,24 @@ internal MergeResult MergeFetchHeads(Signature merger, MergeOptions options) if (fetchHeads.Length == 0) { var expectedRef = this.Head.UpstreamBranchCanonicalName; - throw new MergeFetchHeadNotFoundException(string.Format(CultureInfo.InvariantCulture, - "The current branch is configured to merge with the reference '{0}' from the remote, but this reference was not fetched.", expectedRef)); + throw new MergeFetchHeadNotFoundException("The current branch is configured to merge with the reference '{0}' from the remote, but this reference was not fetched.", + expectedRef); } - GitMergeHeadHandle[] mergeHeadHandles = fetchHeads.Select(fetchHead => - Proxy.git_merge_head_from_fetchhead(Handle, fetchHead.RemoteCanonicalName, fetchHead.Url, fetchHead.Target.Id.Oid)).ToArray(); + AnnotatedCommitHandle[] annotatedCommitHandles = fetchHeads.Select(fetchHead => + Proxy.git_annotated_commit_from_fetchhead(Handle, fetchHead.RemoteCanonicalName, fetchHead.Url, fetchHead.Target.Id.Oid)).ToArray(); try { // Perform the merge. - return Merge(mergeHeadHandles, merger, options); + return Merge(annotatedCommitHandles, merger, options); } finally { // Cleanup. - foreach (GitMergeHeadHandle mergeHeadHandle in mergeHeadHandles) + foreach (AnnotatedCommitHandle annotatedCommitHandle in annotatedCommitHandles) { - mergeHeadHandle.Dispose(); + annotatedCommitHandle.Dispose(); } } } @@ -1108,8 +1322,8 @@ public RevertResult Revert(Commit commit, Signature reverter, RevertOptions opti { Version = 1, MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, RenameThreshold = (uint)options.RenameThreshold, TargetLimit = (uint)options.TargetLimit, }; @@ -1130,7 +1344,7 @@ public RevertResult Revert(Commit commit, Signature reverter, RevertOptions opti // Check if the revert generated any changes // and set the revert status accordingly - bool anythingToRevert = Index.RetrieveStatus( + bool anythingToRevert = RetrieveStatus( new StatusOptions() { DetectRenamesInIndex = false, @@ -1192,8 +1406,8 @@ public CherryPickResult CherryPick(Commit commit, Signature committer, CherryPic { Version = 1, MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, RenameThreshold = (uint)options.RenameThreshold, TargetLimit = (uint)options.TargetLimit, }; @@ -1236,25 +1450,25 @@ private FastForwardStrategy FastForwardStrategyFromMergePreference(GitMergePrefe case GitMergePreference.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY: return FastForwardStrategy.FastForwardOnly; case GitMergePreference.GIT_MERGE_PREFERENCE_NO_FASTFORWARD: - return FastForwardStrategy.NoFastFoward; + return FastForwardStrategy.NoFastForward; default: - throw new InvalidOperationException(String.Format("Unknown merge preference: {0}", preference)); + throw new InvalidOperationException(string.Format("Unknown merge preference: {0}", preference)); } } /// /// Internal implementation of merge. /// - /// Merge heads to operate on. + /// Merge heads to operate on. /// The of who is performing the merge. /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. /// The of the merge. - private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, MergeOptions options) + private MergeResult Merge(AnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) { GitMergeAnalysis mergeAnalysis; GitMergePreference mergePreference; - Proxy.git_merge_analysis(Handle, mergeHeads, out mergeAnalysis, out mergePreference); + Proxy.git_merge_analysis(Handle, annotatedCommits, out mergeAnalysis, out mergePreference); MergeResult mergeResult = null; @@ -1266,34 +1480,34 @@ private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, Mer FastForwardStrategy fastForwardStrategy = (options.FastForwardStrategy != FastForwardStrategy.Default) ? options.FastForwardStrategy : FastForwardStrategyFromMergePreference(mergePreference); - switch(fastForwardStrategy) + switch (fastForwardStrategy) { case FastForwardStrategy.Default: if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD)) { - if (mergeHeads.Length != 1) + if (annotatedCommits.Length != 1) { // We should not reach this code unless there is a bug somewhere. throw new LibGit2SharpException("Unable to perform Fast-Forward merge with mith multiple merge heads."); } - mergeResult = FastForwardMerge(mergeHeads[0], merger, options); + mergeResult = FastForwardMerge(annotatedCommits[0], options); } else if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL)) { - mergeResult = NormalMerge(mergeHeads, merger, options); + mergeResult = NormalMerge(annotatedCommits, merger, options); } break; case FastForwardStrategy.FastForwardOnly: if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD)) { - if (mergeHeads.Length != 1) + if (annotatedCommits.Length != 1) { // We should not reach this code unless there is a bug somewhere. throw new LibGit2SharpException("Unable to perform Fast-Forward merge with mith multiple merge heads."); } - mergeResult = FastForwardMerge(mergeHeads[0], merger, options); + mergeResult = FastForwardMerge(annotatedCommits[0], options); } else { @@ -1302,21 +1516,21 @@ private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, Mer throw new NonFastForwardException("Cannot perform fast-forward merge."); } break; - case FastForwardStrategy.NoFastFoward: + case FastForwardStrategy.NoFastForward: if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL)) { - mergeResult = NormalMerge(mergeHeads, merger, options); + mergeResult = NormalMerge(annotatedCommits, merger, options); } break; default: throw new NotImplementedException( - string.Format(CultureInfo.InvariantCulture, "Unknown fast forward strategy: {0}", mergeAnalysis)); + string.Format(CultureInfo.InvariantCulture, "Unknown fast forward strategy: {0}", fastForwardStrategy)); } if (mergeResult == null) { throw new NotImplementedException( - string.Format(CultureInfo.InvariantCulture, "Unknown merge analysis: {0}", options.FastForwardStrategy)); + string.Format(CultureInfo.InvariantCulture, "Unknown merge analysis: {0}", mergeAnalysis)); } return mergeResult; @@ -1325,29 +1539,51 @@ private MergeResult Merge(GitMergeHeadHandle[] mergeHeads, Signature merger, Mer /// /// Perform a normal merge (i.e. a non-fast-forward merge). /// - /// The merge head handles to merge. + /// The merge head handles to merge. /// The of who is performing the merge. /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. /// The of the merge. - private MergeResult NormalMerge(GitMergeHeadHandle[] mergeHeads, Signature merger, MergeOptions options) + private MergeResult NormalMerge(AnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) { MergeResult mergeResult; + GitMergeFlag treeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES + : GitMergeFlag.GIT_MERGE_NORMAL; + + if (options.FailOnConflict) + { + treeFlags |= GitMergeFlag.GIT_MERGE_FAIL_ON_CONFLICT; + } + + if (options.SkipReuc) + { + treeFlags |= GitMergeFlag.GIT_MERGE_SKIP_REUC; + } + + var fileFlags = options.IgnoreWhitespaceChange + ? GitMergeFileFlag.GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE + : GitMergeFileFlag.GIT_MERGE_FILE_DEFAULT; var mergeOptions = new GitMergeOpts - { - Version = 1, - MergeFileFavorFlags = options.MergeFileFavor, - MergeTreeFlags = options.FindRenames ? GitMergeTreeFlags.GIT_MERGE_TREE_FIND_RENAMES : - GitMergeTreeFlags.GIT_MERGE_TREE_NORMAL, - RenameThreshold = (uint) options.RenameThreshold, - TargetLimit = (uint) options.TargetLimit, - }; + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = treeFlags, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + FileFlags = fileFlags + }; + bool earlyStop; using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) { var checkoutOpts = checkoutOptionsWrapper.Options; - Proxy.git_merge(Handle, mergeHeads, mergeOptions, checkoutOpts); + Proxy.git_merge(Handle, annotatedCommits, mergeOptions, checkoutOpts, out earlyStop); + } + + if (earlyStop) + { + return new MergeResult(MergeStatus.Conflicts); } if (Index.IsFullyMerged) @@ -1372,14 +1608,13 @@ private MergeResult NormalMerge(GitMergeHeadHandle[] mergeHeads, Signature merge /// /// Perform a fast-forward merge. /// - /// The merge head handle to fast-forward merge. - /// The of who is performing the merge. + /// The merge head handle to fast-forward merge. /// Options controlling merge behavior. /// The of the merge. - private MergeResult FastForwardMerge(GitMergeHeadHandle mergeHead, Signature merger, MergeOptions options) + private MergeResult FastForwardMerge(AnnotatedCommitHandle annotatedCommit, MergeOptions options) { - ObjectId id = Proxy.git_merge_head_id(mergeHead); - Commit fastForwardCommit = (Commit) Lookup(id, ObjectType.Commit); + ObjectId id = Proxy.git_annotated_commit_id(annotatedCommit); + Commit fastForwardCommit = (Commit)Lookup(id, ObjectType.Commit); Ensure.GitObjectIsNotNull(fastForwardCommit, id.Sha); CheckoutTree(fastForwardCommit.Tree, null, new FastForwardCheckoutOptionsAdapter(options)); @@ -1393,12 +1628,12 @@ private MergeResult FastForwardMerge(GitMergeHeadHandle mergeHead, Signature mer if (reference == null) { // Reference does not exist, create it. - Refs.Add(Refs.Head.TargetIdentifier, fastForwardCommit.Id, merger, refLogEntry); + Refs.Add(Refs.Head.TargetIdentifier, fastForwardCommit.Id, refLogEntry); } else { // Update target reference. - Refs.UpdateTarget(reference, fastForwardCommit.Id.Sha, merger, refLogEntry); + Refs.UpdateTarget(reference, fastForwardCommit.Id.Sha, refLogEntry); } return new MergeResult(MergeStatus.FastForward, fastForwardCommit); @@ -1422,11 +1657,6 @@ internal StringComparer PathComparer get { return pathCase.Value.Comparer; } } - internal bool PathStartsWith(string path, string value) - { - return pathCase.Value.StartsWith(path, value); - } - internal FilePath[] ToFilePaths(IEnumerable paths) { if (paths == null) @@ -1440,7 +1670,7 @@ internal FilePath[] ToFilePaths(IEnumerable paths) { if (string.IsNullOrEmpty(path)) { - throw new ArgumentException("At least one provided path is either null or empty.", "paths"); + throw new ArgumentException("At least one provided path is either null or empty.", nameof(paths)); } filePaths.Add(this.BuildRelativePathFrom(path)); @@ -1448,20 +1678,118 @@ internal FilePath[] ToFilePaths(IEnumerable paths) if (filePaths.Count == 0) { - throw new ArgumentException("No path has been provided.", "paths"); + throw new ArgumentException("No path has been provided.", nameof(paths)); } return filePaths.ToArray(); } + /// + /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commit. + /// + /// The relative path within the working directory to the file. + /// A representing the state of the parameter. + public FileStatus RetrieveStatus(string filePath) + { + Ensure.ArgumentNotNullOrEmptyString(filePath, "filePath"); + + string relativePath = this.BuildRelativePathFrom(filePath); + + return Proxy.git_status_file(Handle, relativePath); + } + + /// + /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commit. + /// + /// If set, the options that control the status investigation. + /// A holding the state of all the files. + public RepositoryStatus RetrieveStatus(StatusOptions options) + { + ReloadFromDisk(); + + return new RepositoryStatus(this, options); + } + + internal void ReloadFromDisk() + { + Proxy.git_index_read(Index.Handle); + } + + internal void AddToIndex(string relativePath) + { + if (!Submodules.TryStage(relativePath, true)) + { + Proxy.git_index_add_bypath(Index.Handle, relativePath); + } + } + + internal string RemoveFromIndex(string relativePath) + { + Proxy.git_index_remove_bypath(Index.Handle, relativePath); + + return relativePath; + } + + internal void UpdatePhysicalIndex() + { + Proxy.git_index_write(Index.Handle); + } + + /// + /// Finds the most recent annotated tag that is reachable from a commit. + /// + /// If the tag points to the commit, then only the tag is shown. Otherwise, + /// it suffixes the tag name with the number of additional commits on top + /// of the tagged object and the abbreviated object name of the most recent commit. + /// + /// + /// Optionally, the parameter allow to tweak the + /// search strategy (considering lightweight tags, or even branches as reference points) + /// and the formatting of the returned identifier. + /// + /// + /// The commit to be described. + /// Determines how the commit will be described. + /// A descriptive identifier for the commit based on the nearest annotated tag. + public string Describe(Commit commit, DescribeOptions options) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(options, "options"); + + return Proxy.git_describe_commit(handle, commit.Id, options); + } + + /// + /// Parse an extended SHA-1 expression and retrieve the object and the reference + /// mentioned in the revision (if any). + /// + /// An extended SHA-1 expression for the object to look up + /// The reference mentioned in the revision (if any) + /// The object which the revision resolves to + public void RevParse(string revision, out Reference reference, out GitObject obj) + { + var handles = Proxy.git_revparse_ext(Handle, revision); + if (handles == null) + { + Ensure.GitObjectIsNotNull(null, revision); + } + + using (var objH = handles.Item1) + using (var refH = handles.Item2) + { + reference = refH.IsInvalid ? null : Reference.BuildFromPtr(refH, this); + obj = GitObject.BuildFrom(this, Proxy.git_object_id(objH), Proxy.git_object_type(objH), PathFromRevparseSpec(revision)); + } + } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "{0} = \"{1}\"", - Info.IsBare ? "Gitdir" : "Workdir", - Info.IsBare ? Info.Path : Info.WorkingDirectory); + "{0} = \"{1}\"", + Info.IsBare ? "Gitdir" : "Workdir", + Info.IsBare ? Info.Path : Info.WorkingDirectory); } } } diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index ab6d3e94a..5d0788c8a 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -24,7 +24,7 @@ public static T Lookup(this IRepository repository, string objectish) where T { EnsureNoGitLink(); - if (typeof (T) == typeof (GitObject)) + if (typeof(T) == typeof(GitObject)) { return (T)repository.Lookup(objectish); } @@ -98,7 +98,7 @@ private static Commit RetrieveHeadCommit(IRepository repository) { Commit commit = repository.Head.Tip; - Ensure.GitObjectIsNotNull(commit, "HEAD", m => new UnbornBranchException(m)); + Ensure.GitObjectIsNotNull(commit, "HEAD"); return commit; } @@ -121,11 +121,12 @@ public static Tag ApplyTag(this IRepository repository, string tagName, string o /// /// The being worked with. /// The name of the branch to create. - /// Identification for use when updating the reflog - /// Message to append to the reflog - public static Branch CreateBranch(this IRepository repository, string branchName, Signature signature = null, string logMessage = null) + public static Branch CreateBranch(this IRepository repository, string branchName) { - return CreateBranch(repository, branchName, "HEAD", signature, logMessage); + var head = repository.Head; + var reflogName = head is DetachedHead ? head.Tip.Sha : head.FriendlyName; + + return CreateBranch(repository, branchName, reflogName); } /// @@ -134,11 +135,9 @@ public static Branch CreateBranch(this IRepository repository, string branchName /// The being worked with. /// The name of the branch to create. /// The commit which should be pointed at by the Branch. - /// Identification for use when updating the reflog - /// Message to append to the reflog - public static Branch CreateBranch(this IRepository repository, string branchName, Commit target, Signature signature = null, string logMessage = null) + public static Branch CreateBranch(this IRepository repository, string branchName, Commit target) { - return repository.Branches.Add(branchName, target, signature, logMessage); + return repository.Branches.Add(branchName, target); } /// @@ -147,160 +146,61 @@ public static Branch CreateBranch(this IRepository repository, string branchName /// The being worked with. /// The name of the branch to create. /// The revparse spec for the target commit. - /// Identification for use when updating the reflog - /// Message to append to the reflog - public static Branch CreateBranch(this IRepository repository, string branchName, string committish, Signature signature = null, string logMessage = null) + public static Branch CreateBranch(this IRepository repository, string branchName, string committish) { - return repository.Branches.Add(branchName, committish, signature, logMessage); + return repository.Branches.Add(branchName, committish); } /// - /// Sets the current to the specified commit and optionally resets the and + /// Sets the current and resets the and /// the content of the working tree to match. /// /// The being worked with. /// Flavor of reset operation to perform. - /// A revparse spec for the target commit object. - /// Identification for use when updating the reflog - /// Message to append to the reflog - public static void Reset(this IRepository repository, ResetMode resetMode, string committish = "HEAD", - Signature signature = null, string logMessage = null) + public static void Reset(this IRepository repository, ResetMode resetMode) { - Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); - - Commit commit = LookUpCommit(repository, committish); - - repository.Reset(resetMode, commit, signature, logMessage); + repository.Reset(resetMode, "HEAD"); } /// - /// Replaces entries in the with entries from the specified commit. + /// Sets the current to the specified commitish and optionally resets the and + /// the content of the working tree to match. /// /// The being worked with. + /// Flavor of reset operation to perform. /// A revparse spec for the target commit object. - /// The list of paths (either files or directories) that should be considered. - /// - /// If set, the passed will be treated as explicit paths. - /// Use these options to determine how unmatched explicit paths should be handled. - /// - public static void Reset(this IRepository repository, string committish = "HEAD", IEnumerable paths = null, ExplicitPathsOptions explicitPathsOptions = null) - { - if (repository.Info.IsBare) - { - throw new BareRepositoryException("Reset is not allowed in a bare repository"); - } - + public static void Reset(this IRepository repository, ResetMode resetMode, string committish) + { Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); Commit commit = LookUpCommit(repository, committish); - repository.Reset(commit, paths, explicitPathsOptions); + repository.Reset(resetMode, commit); } private static Commit LookUpCommit(IRepository repository, string committish) { GitObject obj = repository.Lookup(committish); Ensure.GitObjectIsNotNull(obj, committish); - return obj.DereferenceToCommit(true); + return obj.Peel(true); } /// /// Stores the content of the as a new into the repository. /// The tip of the will be used as the parent of this new Commit. /// Once the commit is created, the will move forward to point at it. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. /// - /// The being worked with. + /// The being worked with. /// The description of why a change was made to the repository. - /// The that specify the commit behavior. - /// The generated . - public static Commit Commit(this IRepository repository, string message, CommitOptions options = null) - { - Signature author = repository.Config.BuildSignature(DateTimeOffset.Now, true); - - return repository.Commit(message, author, options); - } - - /// - /// Stores the content of the as a new into the repository. - /// The tip of the will be used as the parent of this new Commit. - /// Once the commit is created, the will move forward to point at it. - /// The Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. - /// - /// The being worked with. /// The of who made the change. - /// The description of why a change was made to the repository. - /// The that specify the commit behavior. + /// The of who added the change to the repository. /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author, CommitOptions options = null) - { - Signature committer = repository.Config.BuildSignature(DateTimeOffset.Now, true); - - return repository.Commit(message, author, committer, options); - } - - /// - /// Fetch from the specified remote. - /// - /// The being worked with. - /// The name of the to fetch from. - /// controlling fetch behavior - public static void Fetch(this IRepository repository, string remoteName, FetchOptions options = null) - { - Ensure.ArgumentNotNull(repository, "repository"); - Ensure.ArgumentNotNullOrEmptyString(remoteName, "remoteName"); - - Remote remote = repository.Network.Remotes.RemoteForName(remoteName, true); - repository.Network.Fetch(remote, options); - } - - /// - /// Checkout the specified , reference or SHA. - /// - /// The being worked with. - /// A revparse spec for the commit or branch to checkout. - /// The identity used for updating the reflog - /// The that was checked out. - public static Branch Checkout(this IRepository repository, string commitOrBranchSpec, Signature signature = null) - { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(commitOrBranchSpec, options, signature); - } - - /// - /// Checkout the commit pointed at by the tip of the specified . - /// - /// If this commit is the current tip of the branch as it exists in the repository, the HEAD - /// will point to this branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// The identity used for updating the reflog - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Branch branch, Signature signature = null) - { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(branch, options, signature); - } - - /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// The identity used for updating the reflog - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Commit commit, Signature signature = null) + public static Commit Commit(this IRepository repository, string message, Signature author, Signature committer) { - CheckoutOptions options = new CheckoutOptions(); - return repository.Checkout(commit, options, signature); + return repository.Commit(message, author, committer, default(CommitOptions)); } - internal static string BuildRelativePathFrom(this Repository repo, string path) + internal static string BuildRelativePathFrom(this IRepository repo, string path) { //TODO: To be removed when libgit2 natively implements this if (!Path.IsPathRooted(path)) @@ -310,16 +210,23 @@ internal static string BuildRelativePathFrom(this Repository repo, string path) string normalizedPath = Path.GetFullPath(path); - if (!repo.PathStartsWith(normalizedPath, repo.Info.WorkingDirectory)) + if (!PathStartsWith(repo, normalizedPath, repo.Info.WorkingDirectory)) { throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to process file '{0}'. This file is not located under the working directory of the repository ('{1}').", - normalizedPath, repo.Info.WorkingDirectory)); + normalizedPath, + repo.Info.WorkingDirectory)); } return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); } + internal static bool PathStartsWith(IRepository repository, string path, string value) + { + var pathCase = new PathCase(repository); + return pathCase.StartsWith(path, value); + } + private static ObjectId DereferenceToCommit(Repository repo, string identifier) { var options = LookUpOptions.DereferenceResultToCommit; @@ -423,7 +330,7 @@ internal static IEnumerable Committishes(this Repository repo, object if (throwIfNotFound) { - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unexpected kind of identifier '{0}'.", identifier)); + throw new LibGit2SharpException("Unexpected kind of identifier '{0}'.", identifier); } yield return null; @@ -464,51 +371,6 @@ public static MergeResult Merge(this IRepository repository, string committish, return repository.Merge(committish, merger, null); } - /// - /// Checkout the tip commit of the specified object. If this commit is the - /// current tip of the branch, will checkout the named branch. Otherwise, will checkout the tip commit - /// as a detached HEAD. - /// - /// The being worked with. - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Branch branch, CheckoutOptions options) - { - return repository.Checkout(branch, options, null); - } - - /// - /// Checkout the specified . - /// - /// Will detach the HEAD and make it point to this commit sha. - /// - /// - /// The being worked with. - /// The to check out. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Commit commit, CheckoutOptions options) - { - return repository.Checkout(commit, options, null); - } - - /// - /// Checkout the specified , reference or SHA. - /// - /// If the committishOrBranchSpec parameter resolves to a branch name, then the checked out HEAD will - /// will point to the branch. Otherwise, the HEAD will be detached, pointing at the commit sha. - /// - /// - /// The being worked with. - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, string committishOrBranchSpec, CheckoutOptions options) - { - return repository.Checkout(committishOrBranchSpec, options, null); - } - /// /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. /// @@ -532,43 +394,7 @@ public static void CheckoutPaths(this IRepository repository, string committishO /// The target commit object. public static void Reset(this IRepository repository, ResetMode resetMode, Commit commit) { - repository.Reset(resetMode, commit, null, null); - } - - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The being worked with. - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - public static void Reset(this IRepository repository, Commit commit, IEnumerable paths) - { - repository.Reset(commit, paths, null); - } - - /// - /// Replaces entries in the with entries from the specified commit. - /// - /// The being worked with. - /// The target commit object. - public static void Reset(this IRepository repository, Commit commit) - { - repository.Reset(commit, null, null); - } - - /// - /// Stores the content of the as a new into the repository. - /// The tip of the will be used as the parent of this new Commit. - /// Once the commit is created, the will move forward to point at it. - /// - /// The being worked with. - /// The description of why a change was made to the repository. - /// The of who made the change. - /// The of who added the change to the repository. - /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author, Signature committer) - { - return repository.Commit(message, author, committer, null); + repository.Reset(resetMode, commit); } /// @@ -617,5 +443,32 @@ public static RevertResult Revert(this IRepository repository, Commit commit, Si { return repository.Revert(commit, reverter, null); } + + /// + /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commit. + /// + /// A holding the state of all the files. + /// The being worked with. + public static RepositoryStatus RetrieveStatus(this IRepository repository) + { + Proxy.git_index_read(repository.Index.Handle); + return new RepositoryStatus((Repository)repository, null); + } + + /// + /// Finds the most recent annotated tag that is reachable from a commit. + /// + /// If the tag points to the commit, then only the tag is shown. Otherwise, + /// it suffixes the tag name with the number of additional commits on top + /// of the tagged object and the abbreviated object name of the most recent commit. + /// + /// + /// The being worked with. + /// The commit to be described. + /// A descriptive identifier for the commit based on the nearest annotated tag. + public static string Describe(this IRepository repository, Commit commit) + { + return repository.Describe(commit, new DescribeOptions()); + } } } diff --git a/LibGit2Sharp/RepositoryInformation.cs b/LibGit2Sharp/RepositoryInformation.cs index 12751906f..436b3198e 100644 --- a/LibGit2Sharp/RepositoryInformation.cs +++ b/LibGit2Sharp/RepositoryInformation.cs @@ -1,5 +1,4 @@ -using System; -using LibGit2Sharp.Core; +using LibGit2Sharp.Core; namespace LibGit2Sharp { @@ -24,7 +23,7 @@ internal RepositoryInformation(Repository repo, bool isBare) FilePath path = Proxy.git_repository_path(repo.Handle); FilePath workingDirectoryPath = Proxy.git_repository_workdir(repo.Handle); - Path = path.Native; + Path = path == null ? null : path.Native; WorkingDirectory = workingDirectoryPath == null ? null : workingDirectoryPath.Native; IsShallow = Proxy.git_repository_is_shallow(repo.Handle); } diff --git a/LibGit2Sharp/RepositoryNotFoundException.cs b/LibGit2Sharp/RepositoryNotFoundException.cs index b07ee913c..e2bc63d8b 100644 --- a/LibGit2Sharp/RepositoryNotFoundException.cs +++ b/LibGit2Sharp/RepositoryNotFoundException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif namespace LibGit2Sharp { @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// The exception that is thrown when a is being built with /// a path that doesn't point at a valid Git repository or workdir. /// +#if NETFRAMEWORK [Serializable] +#endif public class RepositoryNotFoundException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public RepositoryNotFoundException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public RepositoryNotFoundException() /// A message that describes the error. public RepositoryNotFoundException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public RepositoryNotFoundException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public RepositoryNotFoundException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public RepositoryNotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +54,7 @@ public RepositoryNotFoundException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected RepositoryNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/RepositoryOperationContext.cs b/LibGit2Sharp/RepositoryOperationContext.cs new file mode 100644 index 000000000..5b67d7269 --- /dev/null +++ b/LibGit2Sharp/RepositoryOperationContext.cs @@ -0,0 +1,78 @@ +namespace LibGit2Sharp +{ + /// + /// Class to convey information about the repository that is being operated on + /// for operations that can recurse into submodules. + /// + public class RepositoryOperationContext + { + /// + /// Needed for mocking. + /// + protected RepositoryOperationContext() + { } + + /// + /// Constructor suitable for use on the repository the main + /// operation is being run on (i.e. the super project, not a submodule). + /// + /// The path of the repository being operated on. + /// The URL that this operation will download from. + internal RepositoryOperationContext(string repositoryPath, string remoteUrl) + : this(repositoryPath, remoteUrl, string.Empty, string.Empty, 0) + { } + + /// + /// Constructor suitable for use on the sub repositories. + /// + /// The path of the repository being operated on. + /// The URL that this operation will download from. + /// The path to the super repository. + /// The logical name of this submodule. + /// The depth of this sub repository from the original super repository. + internal RepositoryOperationContext( + string repositoryPath, + string remoteUrl, + string parentRepositoryPath, + string submoduleName, int recursionDepth) + { + RepositoryPath = repositoryPath; + RemoteUrl = remoteUrl; + ParentRepositoryPath = parentRepositoryPath; + SubmoduleName = submoduleName; + RecursionDepth = recursionDepth; + } + + /// + /// If this is a submodule repository, the full path to the parent + /// repository. If this is not a submodule repository, then + /// this is empty. + /// + public virtual string ParentRepositoryPath { get; private set; } + + /// + /// The recursion depth for the current repository being operated on + /// with respect to the repository the original operation was run + /// against. The initial repository has a recursion depth of 0, + /// the 1st level of subrepositories has a recursion depth of 1. + /// + public virtual int RecursionDepth { get; private set; } + + /// + /// The remote URL this operation will work against, if any. + /// + public virtual string RemoteUrl { get; private set; } + + /// + /// Full path of the repository. + /// + public virtual string RepositoryPath { get; private set; } + + /// + /// The submodule's logical name in the parent repository, if this is a + /// submodule repository. If this is not a submodule repository, then + /// this is empty. + /// + public virtual string SubmoduleName { get; private set; } + } +} diff --git a/LibGit2Sharp/RepositoryOptions.cs b/LibGit2Sharp/RepositoryOptions.cs index 12a5751cf..55692a663 100644 --- a/LibGit2Sharp/RepositoryOptions.cs +++ b/LibGit2Sharp/RepositoryOptions.cs @@ -1,4 +1,6 @@ -namespace LibGit2Sharp +using System; + +namespace LibGit2Sharp { /// /// Provides optional additional information to the Repository to be opened. @@ -27,30 +29,13 @@ public sealed class RepositoryOptions public string IndexPath { get; set; } /// - /// Overrides the probed location of the Global configuration file of a repository. - /// - /// The path has either to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// - /// - public string GlobalConfigurationLocation { get; set; } - - /// - /// Overrides the probed location of the XDG configuration file of a repository. - /// - /// The path has either to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// - /// - public string XdgConfigurationLocation { get; set; } - - /// - /// Overrides the probed location of the System configuration file of a repository. + /// Overrides the default identity to be used when creating reflog entries. /// - /// The path has to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. + /// When unset the identity will be retreived from the repository's configuration. + /// When no identity can be found in the repository configuration stores, a fake + /// identity ("unknown" as both name and email), will be used. /// /// - public string SystemConfigurationLocation { get; set; } + public Identity Identity { get; set; } } } diff --git a/LibGit2Sharp/RepositoryStatus.cs b/LibGit2Sharp/RepositoryStatus.cs index 235f514d7..cc1c6e7e0 100644 --- a/LibGit2Sharp/RepositoryStatus.cs +++ b/LibGit2Sharp/RepositoryStatus.cs @@ -26,6 +26,7 @@ public class RepositoryStatus : IEnumerable private readonly List ignored = new List(); private readonly List renamedInIndex = new List(); private readonly List renamedInWorkDir = new List(); + private readonly List unaltered = new List(); private readonly bool isDirty; private readonly IDictionary> dispatcher = Build(); @@ -33,17 +34,17 @@ public class RepositoryStatus : IEnumerable private static IDictionary> Build() { return new Dictionary> - { - { FileStatus.Untracked, (rs, s) => rs.untracked.Add(s) }, - { FileStatus.Modified, (rs, s) => rs.modified.Add(s) }, - { FileStatus.Missing, (rs, s) => rs.missing.Add(s) }, - { FileStatus.Added, (rs, s) => rs.added.Add(s) }, - { FileStatus.Staged, (rs, s) => rs.staged.Add(s) }, - { FileStatus.Removed, (rs, s) => rs.removed.Add(s) }, - { FileStatus.RenamedInIndex, (rs, s) => rs.renamedInIndex.Add(s) }, - { FileStatus.Ignored, (rs, s) => rs.ignored.Add(s) }, - { FileStatus.RenamedInWorkDir, (rs, s) => rs.renamedInWorkDir.Add(s) } - }; + { + { FileStatus.NewInWorkdir, (rs, s) => rs.untracked.Add(s) }, + { FileStatus.ModifiedInWorkdir, (rs, s) => rs.modified.Add(s) }, + { FileStatus.DeletedFromWorkdir, (rs, s) => rs.missing.Add(s) }, + { FileStatus.NewInIndex, (rs, s) => rs.added.Add(s) }, + { FileStatus.ModifiedInIndex, (rs, s) => rs.staged.Add(s) }, + { FileStatus.DeletedFromIndex, (rs, s) => rs.removed.Add(s) }, + { FileStatus.RenamedInIndex, (rs, s) => rs.renamedInIndex.Add(s) }, + { FileStatus.Ignored, (rs, s) => rs.ignored.Add(s) }, + { FileStatus.RenamedInWorkdir, (rs, s) => rs.renamedInWorkDir.Add(s) }, + }; } /// @@ -52,36 +53,22 @@ private static IDictionary> Bu protected RepositoryStatus() { } - internal RepositoryStatus(Repository repo, StatusOptions options) + internal unsafe RepositoryStatus(Repository repo, StatusOptions options) { statusEntries = new List(); using (GitStatusOptions coreOptions = CreateStatusOptions(options ?? new StatusOptions())) - using (StatusListSafeHandle list = Proxy.git_status_list_new(repo.Handle, coreOptions)) + using (StatusListHandle list = Proxy.git_status_list_new(repo.Handle, coreOptions)) { int count = Proxy.git_status_list_entrycount(list); for (int i = 0; i < count; i++) { - StatusEntrySafeHandle e = Proxy.git_status_byindex(list, i); - GitStatusEntry entry = e.MarshalAsGitStatusEntry(); - - GitDiffDelta deltaHeadToIndex = null; - GitDiffDelta deltaIndexToWorkDir = null; - - if (entry.HeadToIndexPtr != IntPtr.Zero) - { - deltaHeadToIndex = entry.HeadToIndexPtr.MarshalAs(); - } - if (entry.IndexToWorkDirPtr != IntPtr.Zero) - { - deltaIndexToWorkDir = entry.IndexToWorkDirPtr.MarshalAs(); - } - - AddStatusEntryForDelta(entry.Status, deltaHeadToIndex, deltaIndexToWorkDir); + git_status_entry* entry = Proxy.git_status_byindex(list, i); + AddStatusEntryForDelta(entry->status, entry->head_to_index, entry->index_to_workdir); } - isDirty = statusEntries.Any(entry => entry.State != FileStatus.Ignored); + isDirty = statusEntries.Any(entry => entry.State != FileStatus.Ignored && entry.State != FileStatus.Unaltered); } } @@ -91,12 +78,18 @@ private static GitStatusOptions CreateStatusOptions(StatusOptions options) { Version = 1, Show = (GitStatusShow)options.Show, - Flags = - GitStatusOptionFlags.IncludeIgnored | - GitStatusOptionFlags.IncludeUntracked | - GitStatusOptionFlags.RecurseUntrackedDirs, }; + if (options.IncludeIgnored) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeIgnored; + } + + if (options.IncludeUntracked) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeUntracked; + } + if (options.DetectRenamesInIndex) { coreOptions.Flags |= @@ -123,44 +116,74 @@ private static GitStatusOptions CreateStatusOptions(StatusOptions options) GitStatusOptionFlags.RecurseIgnoredDirs; } + if (options.RecurseUntrackedDirs) + { + coreOptions.Flags |= + GitStatusOptionFlags.RecurseUntrackedDirs; + } + + if (options.PathSpec != null) + { + coreOptions.PathSpec = GitStrArrayManaged.BuildFrom(options.PathSpec); + } + + if (options.DisablePathSpecMatch) + { + coreOptions.Flags |= + GitStatusOptionFlags.DisablePathspecMatch; + } + + if (options.IncludeUnaltered) + { + coreOptions.Flags |= + GitStatusOptionFlags.IncludeUnmodified; + } + return coreOptions; } - private void AddStatusEntryForDelta(FileStatus gitStatus, GitDiffDelta deltaHeadToIndex, GitDiffDelta deltaIndexToWorkDir) + private unsafe void AddStatusEntryForDelta(FileStatus gitStatus, git_diff_delta* deltaHeadToIndex, git_diff_delta* deltaIndexToWorkDir) { RenameDetails headToIndexRenameDetails = null; RenameDetails indexToWorkDirRenameDetails = null; if ((gitStatus & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex) { - headToIndexRenameDetails = new RenameDetails( - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.OldFile.Path).Native, - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native, - (int)deltaHeadToIndex.Similarity); + headToIndexRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaHeadToIndex->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaHeadToIndex->new_file.Path), + (int)deltaHeadToIndex->similarity); } - if ((gitStatus & FileStatus.RenamedInWorkDir) == FileStatus.RenamedInWorkDir) + if ((gitStatus & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) { - indexToWorkDirRenameDetails = new RenameDetails( - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.OldFile.Path).Native, - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native, - (int)deltaIndexToWorkDir.Similarity); + indexToWorkDirRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->new_file.Path), + (int)deltaIndexToWorkDir->similarity); } - var filePath = (deltaIndexToWorkDir != null) ? - LaxFilePathMarshaler.FromNative(deltaIndexToWorkDir.NewFile.Path).Native : - LaxFilePathMarshaler.FromNative(deltaHeadToIndex.NewFile.Path).Native; + var filePath = LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir != null ? + deltaIndexToWorkDir->new_file.Path : + deltaHeadToIndex->new_file.Path); StatusEntry statusEntry = new StatusEntry(filePath, gitStatus, headToIndexRenameDetails, indexToWorkDirRenameDetails); - foreach (KeyValuePair> kvp in dispatcher) + if (gitStatus == FileStatus.Unaltered) + { + unaltered.Add(statusEntry); + } + else { - if (!gitStatus.HasFlag(kvp.Key)) + foreach (KeyValuePair> kvp in dispatcher) { - continue; - } + if (!gitStatus.HasFlag(kvp.Key)) + { + continue; + } - kvp.Value(this, statusEntry); + kvp.Value(this, statusEntry); + } } statusEntries.Add(statusEntry); @@ -278,6 +301,14 @@ public virtual IEnumerable RenamedInWorkDir get { return renamedInWorkDir; } } + /// + /// List of files that were unmodified in the working directory. + /// + public virtual IEnumerable Unaltered + { + get { return unaltered; } + } + /// /// True if the index or the working directory has been altered since the last commit. False otherwise. /// @@ -290,12 +321,15 @@ private string DebuggerDisplay { get { - return string.Format( - CultureInfo.InvariantCulture, - "+{0} ~{1} -{2} | +{3} ~{4} -{5} | i{6}", - Added.Count(), Staged.Count(), Removed.Count(), - Untracked.Count(), Modified.Count(), Missing.Count(), - Ignored.Count()); + return string.Format(CultureInfo.InvariantCulture, + "+{0} ~{1} -{2} | +{3} ~{4} -{5} | i{6}", + Added.Count(), + Staged.Count(), + Removed.Count(), + Untracked.Count(), + Modified.Count(), + Missing.Count(), + Ignored.Count()); } } } diff --git a/LibGit2Sharp/ResetMode.cs b/LibGit2Sharp/ResetMode.cs index fa52d1e6f..f15028918 100644 --- a/LibGit2Sharp/ResetMode.cs +++ b/LibGit2Sharp/ResetMode.cs @@ -1,7 +1,7 @@ namespace LibGit2Sharp { /// - /// Specifies the kind of operation that should perform. + /// Specifies the kind of operation that should perform. /// public enum ResetMode { diff --git a/LibGit2Sharp/RevertOptions.cs b/LibGit2Sharp/RevertOptions.cs index 8b1a103bc..882afb082 100644 --- a/LibGit2Sharp/RevertOptions.cs +++ b/LibGit2Sharp/RevertOptions.cs @@ -1,61 +1,16 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options controlling Revert behavior. /// - public sealed class RevertOptions : IConvertableToGitCheckoutOpts + public sealed class RevertOptions : MergeAndCheckoutOptionsBase { /// /// Initializes a new instance of the class. /// By default the revert will be committed if there are no conflicts. /// public RevertOptions() - { - CommitOnSuccess = true; - - FindRenames = true; - - // TODO: libgit2 should provide reasonable defaults for these - // values, but it currently does not. - RenameThreshold = 50; - TargetLimit = 200; - } - - /// - /// The Flags specifying what conditions are - /// reported through the OnCheckoutNotify delegate. - /// - public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } - - /// - /// Delegate that checkout progress will be reported through. - /// - public CheckoutProgressHandler OnCheckoutProgress { get; set; } - - /// - /// Delegate that checkout will notify callers of - /// certain conditions. The conditions that are reported is - /// controlled with the CheckoutNotifyFlags property. - /// - public CheckoutNotifyHandler OnCheckoutNotify { get; set; } - - /// - /// Commit changes if there are no conflicts and the revert results - /// in changes. - /// - /// Following command line behavior, if the revert results in no - /// changes, then Revert will cleanup the repository state if - /// is true (i.e. the repository - /// will not be left in a "revert in progress" state). - /// If is false and there are no - /// changes to revert, then the repository will be left in - /// the "revert in progress" state. - /// - /// - public bool CommitOnSuccess { get; set; } + { } /// /// When reverting a merge commit, the parent number to consider as @@ -68,57 +23,5 @@ public RevertOptions() /// /// public int Mainline { get; set; } - - /// - /// How to handle conflicts encountered during a merge. - /// - public MergeFileFavor MergeFileFavor { get; set; } - - /// - /// How Checkout should handle writing out conflicting index entries. - /// - public CheckoutFileConflictStrategy FileConflictStrategy { get; set; } - - /// - /// Find renames. Default is true. - /// - public bool FindRenames { get; set; } - - /// - /// Similarity to consider a file renamed (default 50). If - /// `FindRenames` is enabled, added files will be compared - /// with deleted files to determine their similarity. Files that are - /// more similar than the rename threshold (percentage-wise) will be - /// treated as a rename. - /// - public int RenameThreshold; - - /// - /// Maximum similarity sources to examine for renames (default 200). - /// If the number of rename candidates (add / delete pairs) is greater - /// than this value, inexact rename detection is aborted. - /// - /// This setting overrides the `merge.renameLimit` configuration value. - /// - public int TargetLimit; - - #region IConvertableToGitCheckoutOpts - - CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() - { - return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); - } - - CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy - { - get - { - return CheckoutStrategy.GIT_CHECKOUT_SAFE | - GitCheckoutOptsWrapper.CheckoutStrategyFromFileConflictStrategy(FileConflictStrategy); - } - } - - #endregion IConvertableToGitCheckoutOpts - } } diff --git a/LibGit2Sharp/RevertResult.cs b/LibGit2Sharp/RevertResult.cs index 813903a76..8f9a270d3 100644 --- a/LibGit2Sharp/RevertResult.cs +++ b/LibGit2Sharp/RevertResult.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Class to report the result of a revert. @@ -39,7 +34,7 @@ internal RevertResult(RevertStatus status, Commit commit = null) public virtual RevertStatus Status { get; private set; } } - /// + /// /// The status of what happened as a result of a revert. /// public enum RevertStatus diff --git a/LibGit2Sharp/RewriteHistoryOptions.cs b/LibGit2Sharp/RewriteHistoryOptions.cs index 031839c38..59a982dc2 100644 --- a/LibGit2Sharp/RewriteHistoryOptions.cs +++ b/LibGit2Sharp/RewriteHistoryOptions.cs @@ -77,5 +77,13 @@ public RewriteHistoryOptions() /// /// public Action OnError { get; set; } + + /// + /// Specifies Commit message prettifying behavior during rewrite. + /// NOTE: Prettifying may result in losing one or multiple lines in the commit message. + /// As such it is recommended to leave this set to false. + /// + /// true if Commit messages are prettified; otherwise, false. + public bool PrettifyMessages { get; set; } } } diff --git a/LibGit2Sharp/SecureUsernamePasswordCredentials.cs b/LibGit2Sharp/SecureUsernamePasswordCredentials.cs new file mode 100644 index 000000000..16427ddf3 --- /dev/null +++ b/LibGit2Sharp/SecureUsernamePasswordCredentials.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that uses to hold username and password credentials for remote repository access. + /// + public sealed class SecureUsernamePasswordCredentials : Credentials + { + /// + /// Callback to acquire a credential object. + /// + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal override int GitCredentialHandler(out IntPtr cred) + { + if (Username == null || Password == null) + { + throw new InvalidOperationException("UsernamePasswordCredentials contains a null Username or Password."); + } + + IntPtr passwordPtr = IntPtr.Zero; + + try + { + passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(Password); + + return NativeMethods.git_cred_userpass_plaintext_new(out cred, Username, Marshal.PtrToStringUni(passwordPtr)); + } + finally + { + Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr); + } + + } + + /// + /// Username for username/password authentication (as in HTTP basic auth). + /// + public string Username { get; set; } + + /// + /// Password for username/password authentication (as in HTTP basic auth). + /// + public SecureString Password { get; set; } + } +} diff --git a/LibGit2Sharp/Signature.cs b/LibGit2Sharp/Signature.cs index 74be26448..7ed7a4916 100644 --- a/LibGit2Sharp/Signature.cs +++ b/LibGit2Sharp/Signature.cs @@ -1,6 +1,5 @@ using System; using System.Globalization; -using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; @@ -18,13 +17,11 @@ public sealed class Signature : IEquatable private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Name, x => x.Email, x => x.When); - internal Signature(IntPtr signaturePtr) + internal unsafe Signature(git_signature* sig) { - var handle = signaturePtr.MarshalAs(); - - name = LaxUtf8Marshaler.FromNative(handle.Name); - email = LaxUtf8Marshaler.FromNative(handle.Email); - when = Epoch.ToDateTimeOffset(handle.When.Time, handle.When.Offset); + name = LaxUtf8Marshaler.FromNative(sig->name); + email = LaxUtf8Marshaler.FromNative(sig->email); + when = DateTimeOffset.FromUnixTimeSeconds(sig->when.time).ToOffset(TimeSpan.FromMinutes(sig->when.offset)); } /// @@ -36,7 +33,7 @@ internal Signature(IntPtr signaturePtr) public Signature(string name, string email, DateTimeOffset when) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNull(email, "email"); + Ensure.ArgumentNotNullOrEmptyString(email, "email"); Ensure.ArgumentDoesNotContainZeroByte(name, "name"); Ensure.ArgumentDoesNotContainZeroByte(email, "email"); @@ -45,7 +42,21 @@ public Signature(string name, string email, DateTimeOffset when) this.when = when; } - internal SignatureSafeHandle BuildHandle() + /// + /// Initializes a new instance of the class. + /// + /// The identity. + /// The when. + public Signature(Identity identity, DateTimeOffset when) + { + Ensure.ArgumentNotNull(identity, "identity"); + + this.name = identity.Name; + this.email = identity.Email; + this.when = when; + } + + internal SignatureHandle BuildHandle() { return Proxy.git_signature_new(name, email, when); } @@ -75,10 +86,10 @@ public DateTimeOffset When } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as Signature); @@ -134,4 +145,23 @@ public override string ToString() return string.Format(CultureInfo.InvariantCulture, "{0} <{1}>", Name, Email); } } + + internal static class SignatureHelpers + { + /// + /// Build the handle for the Signature, or return a handle + /// to an empty signature. + /// + /// + /// + public static unsafe SignatureHandle SafeBuildHandle(this Signature signature) + { + if (signature == null) + { + return new SignatureHandle(null, false); + } + + return signature.BuildHandle(); + } + } } diff --git a/LibGit2Sharp/SignatureExtensions.cs b/LibGit2Sharp/SignatureExtensions.cs deleted file mode 100644 index 7c3f868c8..000000000 --- a/LibGit2Sharp/SignatureExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads for a - /// - internal static class SignatureExtensions - { - /// - /// If the signature is null, return the default using configuration values. - /// - /// The signature to test - /// The configuration to query for default values - /// A valid - public static Signature OrDefault(this Signature signature, Configuration config) - { - return signature ?? config.BuildSignature(DateTimeOffset.Now); - } - } -} diff --git a/LibGit2Sharp/SignatureInfo.cs b/LibGit2Sharp/SignatureInfo.cs new file mode 100644 index 000000000..71db67a7b --- /dev/null +++ b/LibGit2Sharp/SignatureInfo.cs @@ -0,0 +1,20 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Structure for holding a signature extracted from a commit or a tag + /// + public struct SignatureInfo + { + /// + /// The signature data, PGP/GPG or otherwise. + /// + public string Signature; + /// + /// The data which was signed. The object contents without the signature part. + /// + public string SignedData; + } +} + diff --git a/LibGit2Sharp/SimilarityOptions.cs b/LibGit2Sharp/SimilarityOptions.cs index 13d26abf2..4d2b0cd95 100644 --- a/LibGit2Sharp/SimilarityOptions.cs +++ b/LibGit2Sharp/SimilarityOptions.cs @@ -81,7 +81,7 @@ public SimilarityOptions() /// public static SimilarityOptions None { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.None}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.None }; } } /// @@ -89,7 +89,7 @@ public static SimilarityOptions None /// public static SimilarityOptions Renames { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.Renames}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Renames }; } } /// @@ -97,7 +97,7 @@ public static SimilarityOptions Renames /// public static SimilarityOptions Exact { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.Exact}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Exact }; } } /// @@ -105,7 +105,7 @@ public static SimilarityOptions Exact /// public static SimilarityOptions Copies { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.Copies}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Copies }; } } /// @@ -113,7 +113,7 @@ public static SimilarityOptions Copies /// public static SimilarityOptions CopiesHarder { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.CopiesHarder}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.CopiesHarder }; } } /// @@ -121,7 +121,7 @@ public static SimilarityOptions CopiesHarder /// public static SimilarityOptions Default { - get { return new SimilarityOptions {RenameDetectionMode = RenameDetectionMode.Default}; } + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Default }; } } /// diff --git a/LibGit2Sharp/SmartSubtransport.cs b/LibGit2Sharp/SmartSubtransport.cs index 2f1f5f623..6160c849b 100644 --- a/LibGit2Sharp/SmartSubtransport.cs +++ b/LibGit2Sharp/SmartSubtransport.cs @@ -1,8 +1,8 @@ using System; -using System.Collections.Generic; -using System.IO; using System.Runtime.InteropServices; +using System.Text; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -49,21 +49,125 @@ public abstract class RpcSmartSubtransport : SmartSubtransport /// public abstract class SmartSubtransport { + internal IntPtr Transport { get; set; } + + /// + /// Call the certificate check callback + /// + /// The certificate to send + /// Whether we consider the certificate to be valid + /// The hostname we connected to + public int CertificateCheck(Certificate cert, bool valid, string hostname) + { + CertificateSsh sshCert = cert as CertificateSsh; + CertificateX509 x509Cert = cert as CertificateX509; + + if (sshCert == null && x509Cert == null) + { + throw new InvalidOperationException("Unsupported certificate type"); + } + + int ret; + if (sshCert != null) + { + var certPtr = sshCert.ToPointer(); + ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname); + Marshal.FreeHGlobal(certPtr); + } + else + { + IntPtr certPtr, dataPtr; + certPtr = x509Cert.ToPointers(out dataPtr); + ret = NativeMethods.git_transport_smart_certificate_check(Transport, certPtr, valid ? 1 : 0, hostname); + Marshal.FreeHGlobal(dataPtr); + Marshal.FreeHGlobal(certPtr); + } + + if (ret > 0 || ret == (int)GitErrorCode.PassThrough) + { + ret = valid ? 0 : -1; + } + + return ret; + } + + /// + /// Acquires credentials. + /// + /// Receives the credentials if the operation is successful. + /// The username. + /// The credential types allowed. The only supported one is . May be empty but should not be null. + /// 0 if successful; a non-zero error code that came from otherwise. + public int AcquireCredentials(out Credentials cred, string user, params Type[] methods) + { + // Convert the user-provided types to libgit2's flags + int allowed = 0; + foreach (var method in methods) + { + if (method == typeof(UsernamePasswordCredentials)) + { + allowed |= (int)GitCredentialType.UserPassPlaintext; + } + else if (method == typeof(DefaultCredentials)) + { + allowed |= (int)GitCredentialType.Default; + } + else + { + throw new InvalidOperationException("Unknown type passes as allowed credential"); + } + } + + IntPtr credHandle = IntPtr.Zero; + int res = Proxy.git_transport_smart_credentials(out credHandle, Transport, user, allowed); + if (res != 0) + { + cred = null; + return res; + } + + if (credHandle == IntPtr.Zero) + { + throw new InvalidOperationException("credentials callback indicated success but returned no credentials"); + } + + unsafe + { + var baseCred = (GitCredential*)credHandle; + switch (baseCred->credtype) + { + case GitCredentialType.UserPassPlaintext: + cred = UsernamePasswordCredentials.FromNative((GitCredentialUserpass*)credHandle); + return 0; + case GitCredentialType.Default: + cred = new DefaultCredentials(); + return 0; + default: + throw new InvalidOperationException("User returned an unkown credential type"); + } + } + } + + /// + /// libgit2 will call an action back with a null url to indicate that + /// it should re-use the prior url; store the url so that we can replay. + /// + private string LastActionUrl { get; set; } + /// /// Invoked by libgit2 to create a connection using this subtransport. /// /// The endpoint to connect to /// The type of connection to create /// A SmartSubtransportStream representing the connection - protected abstract SmartSubtransportStream Action(String url, GitSmartSubtransportAction action); + protected abstract SmartSubtransportStream Action(string url, GitSmartSubtransportAction action); /// /// Invoked by libgit2 when this subtransport is no longer needed, but may be re-used in the future. /// Override this method to add additional cleanup steps to your subclass. Be sure to call base.Close(). /// protected virtual void Close() - { - } + { } /// /// Invoked by libgit2 when this subtransport is being freed. Override this method to add additional @@ -123,45 +227,59 @@ private static int Action( stream = IntPtr.Zero; SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; - String urlAsString = LaxUtf8Marshaler.FromNative(url); + string urlAsString = LaxUtf8Marshaler.FromNative(url); - if (null != t && - !String.IsNullOrEmpty(urlAsString)) + if (t == null) { - try - { - stream = t.Action(urlAsString, action).GitSmartTransportStreamPointer; + Proxy.git_error_set_str(GitErrorCategory.Net, "no subtransport provided"); + return (int)GitErrorCode.Error; + } - return 0; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + if (string.IsNullOrEmpty(urlAsString)) + { + urlAsString = t.LastActionUrl; } - return (int)GitErrorCode.Error; + if (string.IsNullOrEmpty(urlAsString)) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "no url provided"); + return (int)GitErrorCode.Error; + } + + try + { + stream = t.Action(urlAsString, action).GitSmartTransportStreamPointer; + t.LastActionUrl = urlAsString; + return 0; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Net, ex); + return (int)GitErrorCode.Error; + } } private static int Close(IntPtr subtransport) { SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; - if (null != t) + if (t == null) { - try - { - t.Close(); - - return 0; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + Proxy.git_error_set_str(GitErrorCategory.Net, "no subtransport provided"); + return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + try + { + t.Close(); + + return 0; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Net, ex); + return (int)GitErrorCode.Error; + } } private static void Free(IntPtr subtransport) @@ -176,7 +294,7 @@ private static void Free(IntPtr subtransport) } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); + Proxy.git_error_set_str(GitErrorCategory.Net, ex); } } } diff --git a/LibGit2Sharp/SmartSubtransportRegistration.cs b/LibGit2Sharp/SmartSubtransportRegistration.cs index d7d7a0adf..d33887122 100644 --- a/LibGit2Sharp/SmartSubtransportRegistration.cs +++ b/LibGit2Sharp/SmartSubtransportRegistration.cs @@ -1,7 +1,8 @@ using System; -using System.Linq; +using System.Reflection; using System.Runtime.InteropServices; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -10,7 +11,7 @@ namespace LibGit2Sharp /// under a particular scheme (eg "http"). /// /// The type of SmartSubtransport to register - public sealed class SmartSubtransportRegistration + public sealed class SmartSubtransportRegistration : SmartSubtransportRegistrationData where T : SmartSubtransport, new() { /// @@ -25,27 +26,6 @@ internal SmartSubtransportRegistration(string scheme) FunctionPointer = CreateFunctionPointer(); } - /// - /// The URI scheme (eg "http") for this transport. - /// - public string Scheme - { - get; - private set; - } - - internal IntPtr RegistrationPointer - { - get; - private set; - } - - internal IntPtr FunctionPointer - { - get; - private set; - } - private IntPtr CreateRegistrationPointer() { var registration = new GitSmartSubtransportRegistration(); @@ -81,19 +61,22 @@ private static class EntryPoints private static int Subtransport( out IntPtr subtransport, - IntPtr transport) + IntPtr transport, + IntPtr payload) { subtransport = IntPtr.Zero; try { - subtransport = new T().GitSmartSubtransportPointer; + var obj = new T(); + obj.Transport = transport; + subtransport = obj.GitSmartSubtransportPointer; return 0; } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); + Proxy.git_error_set_str(GitErrorCategory.Net, ex); } return (int)GitErrorCode.Error; @@ -112,7 +95,7 @@ private static int Transport( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); + Proxy.git_error_set_str(GitErrorCategory.Net, ex); } return (int)GitErrorCode.Error; diff --git a/LibGit2Sharp/SmartSubtransportRegistrationData.cs b/LibGit2Sharp/SmartSubtransportRegistrationData.cs new file mode 100644 index 000000000..dbf612adb --- /dev/null +++ b/LibGit2Sharp/SmartSubtransportRegistrationData.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Information about a smart subtransport registration. + /// + public abstract class SmartSubtransportRegistrationData + { + /// + /// The URI scheme for this transport, for example "http" or "ssh". + /// + public string Scheme { get; internal set; } + + internal IntPtr RegistrationPointer { get; set; } + + internal IntPtr FunctionPointer { get; set; } + } +} diff --git a/LibGit2Sharp/SmartSubtransportStream.cs b/LibGit2Sharp/SmartSubtransportStream.cs index 7048370ea..008d1fcd0 100644 --- a/LibGit2Sharp/SmartSubtransportStream.cs +++ b/LibGit2Sharp/SmartSubtransportStream.cs @@ -44,29 +44,35 @@ protected virtual void Free() } /// - /// Requests that the stream write the next length bytes of the stream to the provided Stream object. + /// Reads from the transport into the provided object. /// - public abstract int Read( - Stream dataStream, - long length, - out long bytesRead); + /// The stream to copy the read bytes into. + /// The number of bytes expected from the underlying transport. + /// Receives the number of bytes actually read. + /// The error code to propagate back to the native code that requested this operation. 0 is expected, and exceptions may be thrown. + public abstract int Read(Stream dataStream, long length, out long bytesRead); /// - /// Requests that the stream write the first length bytes of the provided Stream object to the stream. + /// Writes the content of a given stream to the transport. /// - public abstract int Write( - Stream dataStream, - long length); + /// The stream with the data to write to the transport. + /// The number of bytes to read from . + /// The error code to propagate back to the native code that requested this operation. 0 is expected, and exceptions may be thrown. + public abstract int Write(Stream dataStream, long length); /// /// The smart transport that this stream represents a connection over. /// public virtual SmartSubtransport SmartTransport { - get - { - return this.subtransport; - } + get { return this.subtransport; } + } + + private Exception StoredError { get; set; } + + internal void SetError(Exception ex) + { + StoredError = ex; } private SmartSubtransport subtransport; @@ -104,6 +110,19 @@ private static class EntryPoints public static GitSmartSubtransportStream.write_callback WriteCallback = new GitSmartSubtransportStream.write_callback(Write); public static GitSmartSubtransportStream.free_callback FreeCallback = new GitSmartSubtransportStream.free_callback(Free); + private static int SetError(SmartSubtransportStream stream, Exception caught) + { + Exception ret = (stream.StoredError != null) ? stream.StoredError : caught; + GitErrorCode errorCode = GitErrorCode.Error; + + if (ret is NativeException) + { + errorCode = ((NativeException)ret).ErrorCode; + } + + return (int)errorCode; + } + private unsafe static int Read( IntPtr stream, IntPtr buffer, @@ -112,65 +131,78 @@ private unsafe static int Read( { bytes_read = UIntPtr.Zero; - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + + if (transportStream == null) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "no transport stream provided"); + return (int)GitErrorCode.Error; + } + + if (buf_size.ToUInt64() >= (ulong)long.MaxValue) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "buffer size is too large"); + return (int)GitErrorCode.Error; + } - if (transportStream != null && - buf_size.ToUInt64() < (ulong)long.MaxValue) + try { - using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, (long)buf_size.ToUInt64(), FileAccess.ReadWrite)) + using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, + (long)buf_size.ToUInt64(), + FileAccess.ReadWrite)) { - try - { - long longBytesRead; + long longBytesRead; - int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead); + int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead); - bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead)); + bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead)); - return toReturn; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + return toReturn; } } - - return (int)GitErrorCode.Error; + catch (Exception ex) + { + return SetError(transportStream, ex); + } } - private static unsafe int Write( - IntPtr stream, - IntPtr buffer, - UIntPtr len) + private static unsafe int Write(IntPtr stream, IntPtr buffer, UIntPtr len) { - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; - if (transportStream != null && - len.ToUInt64() < (ulong)long.MaxValue) + if (transportStream == null) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "no transport stream provided"); + return (int)GitErrorCode.Error; + } + + if (len.ToUInt64() >= (ulong)long.MaxValue) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "write length is too large"); + return (int)GitErrorCode.Error; + } + + try { long length = (long)len.ToUInt64(); using (UnmanagedMemoryStream dataStream = new UnmanagedMemoryStream((byte*)buffer, length)) { - try - { - return transportStream.Write(dataStream, length); - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); - } + return transportStream.Write(dataStream, length); } } - - return (int)GitErrorCode.Error; + catch (Exception ex) + { + return SetError(transportStream, ex); + } } - private static void Free( - IntPtr stream) + private static void Free(IntPtr stream) { - SmartSubtransportStream transportStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; if (transportStream != null) { @@ -180,7 +212,7 @@ private static void Free( } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Net, ex); + Proxy.git_error_set_str(GitErrorCategory.Net, ex); } } } diff --git a/LibGit2Sharp/StageOptions.cs b/LibGit2Sharp/StageOptions.cs index 7701b8948..4e5d72608 100644 --- a/LibGit2Sharp/StageOptions.cs +++ b/LibGit2Sharp/StageOptions.cs @@ -1,6 +1,4 @@ -using System; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Options to define file staging behavior. diff --git a/LibGit2Sharp/StashApplyOptions.cs b/LibGit2Sharp/StashApplyOptions.cs new file mode 100644 index 000000000..624546f90 --- /dev/null +++ b/LibGit2Sharp/StashApplyOptions.cs @@ -0,0 +1,47 @@ +using System; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// The options to be used for stash application. + /// + public sealed class StashApplyOptions + { + /// + /// for controlling checkout index reinstating./> + /// + /// The flags. + public StashApplyModifiers ApplyModifiers { get; set; } + + /// + /// controlling checkout behavior. + /// + /// The checkout options. + public CheckoutOptions CheckoutOptions { get; set; } + + /// + /// for controlling stash application progress./> + /// + /// The progress handler. + public StashApplyProgressHandler ProgressHandler { get; set; } + } + + /// + /// The flags which control whether the index should be reinstated. + /// + [Flags] + public enum StashApplyModifiers + { + /// + /// Default. Will apply the stash and result in an index with conflicts + /// if any arise. + /// + Default = 0, + + /// + /// In case any conflicts arise, this will not apply the stash. + /// + ReinstateIndex = (1 << 0), + } +} diff --git a/LibGit2Sharp/StashApplyProgress.cs b/LibGit2Sharp/StashApplyProgress.cs new file mode 100644 index 000000000..329e88741 --- /dev/null +++ b/LibGit2Sharp/StashApplyProgress.cs @@ -0,0 +1,48 @@ +namespace LibGit2Sharp +{ + /// + /// The current progress of the stash application. + /// + public enum StashApplyProgress + { + /// + /// Not passed by the callback. Used as dummy value. + /// + None = 0, + + /// + /// Loading the stashed data from the object database. + /// + LoadingStash, + + /// + /// The stored index is being analyzed. + /// + AnalyzeIndex, + + /// + /// The modified files are being analyzed. + /// + AnalyzeModified, + + /// + /// The untracked and ignored files are being analyzed. + /// + AnalyzeUntracked, + + /// + /// The untracked files are being written to disk. + /// + CheckoutUntracked, + + /// + /// The modified files are being written to disk. + /// + CheckoutModified, + + /// + /// The stash was applied successfully. + /// + Done, + } +} diff --git a/LibGit2Sharp/StashApplyStatus.cs b/LibGit2Sharp/StashApplyStatus.cs new file mode 100644 index 000000000..5a316b7e2 --- /dev/null +++ b/LibGit2Sharp/StashApplyStatus.cs @@ -0,0 +1,28 @@ +namespace LibGit2Sharp +{ + /// + /// The result of a stash application operation. + /// + public enum StashApplyStatus + { + /// + /// The stash application was successful. + /// + Applied, + + /// + /// The stash application ended up with conflicts. + /// + Conflicts, + + /// + /// The stash index given was not found. + /// + NotFound, + + /// + /// The stash application was aborted due to uncommitted changes in the index. + /// + UncommittedChanges, + } +} diff --git a/LibGit2Sharp/StashCollection.cs b/LibGit2Sharp/StashCollection.cs index ffe137a5b..42162ada5 100644 --- a/LibGit2Sharp/StashCollection.cs +++ b/LibGit2Sharp/StashCollection.cs @@ -42,8 +42,9 @@ internal StashCollection(Repository repo) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return Proxy.git_stash_foreach(repo.Handle, - (index, message, commitId) => new Stash(repo, new ObjectId(commitId), index)).GetEnumerator(); + Func resultSelector = (index, message, commitId) => new Stash(repo, new ObjectId(commitId), index); + + return Proxy.git_stash_foreach(repo.Handle, resultSelector).GetEnumerator(); } /// @@ -66,16 +67,52 @@ public virtual Stash this[int index] { if (index < 0) { - throw new ArgumentOutOfRangeException("index", "The passed index must be a positive integer."); + throw new ArgumentOutOfRangeException(nameof(index), "The passed index must be a positive integer."); } - GitObject stashCommit = repo.Lookup( - string.Format(CultureInfo.InvariantCulture, "stash@{{{0}}}", index), GitObjectType.Commit, LookUpOptions.None); + GitObject stashCommit = repo.Lookup(string.Format(CultureInfo.InvariantCulture, + "stash@{{{0}}}", + index), + GitObjectType.Commit, + LookUpOptions.None); - return stashCommit == null ? null : new Stash(repo, stashCommit.Id, index); + return stashCommit == null + ? null + : new Stash(repo, stashCommit.Id, index); } } + /// + /// Creates a stash with the specified message. + /// + /// The of the user who stashes + /// the newly created + public virtual Stash Add(Signature stasher) + { + return Add(stasher, null, StashModifiers.Default); + } + /// + /// Creates a stash with the specified message. + /// + /// The of the user who stashes + /// A combination of flags + /// the newly created + public virtual Stash Add(Signature stasher, StashModifiers options) + { + return Add(stasher, null, options); + } + + /// + /// Creates a stash with the specified message. + /// + /// The of the user who stashes + /// The message of the stash. + /// the newly created + public virtual Stash Add(Signature stasher, string message) + { + return Add(stasher, message, StashModifiers.Default); + } + /// /// Creates a stash with the specified message. /// @@ -83,7 +120,7 @@ public virtual Stash this[int index] /// The message of the stash. /// A combination of flags /// the newly created - public virtual Stash Add(Signature stasher, string message = null, StashModifiers options = StashModifiers.Default) + public virtual Stash Add(Signature stasher, string message, StashModifiers options) { Ensure.ArgumentNotNull(stasher, "stasher"); @@ -100,6 +137,92 @@ public virtual Stash Add(Signature stasher, string message = null, StashModifier return new Stash(repo, oid, 0); } + /// + /// Applies a single stashed state from the stash list + /// + /// the index of the stash to remove (0 being the most recent one). + /// the options to use for checking out the stash. + public virtual StashApplyStatus Apply(int index, StashApplyOptions options) + { + if (index < 0) + { + throw new ArgumentException("The passed index must be a positive integer.", nameof(index)); + } + + if (options == null) + { + options = new StashApplyOptions(); + } + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options.CheckoutOptions ?? new CheckoutOptions())) + { + var opts = new GitStashApplyOpts + { + CheckoutOptions = checkoutOptionsWrapper.Options, + Flags = options.ApplyModifiers, + }; + + if (options.ProgressHandler != null) + { + opts.ApplyProgressCallback = (progress, payload) => options.ProgressHandler(progress) ? 0 : -1; + } + + return Proxy.git_stash_apply(repo.Handle, index, opts); + } + } + + /// + /// Applies a single stashed state from the stash list using the default options. + /// + /// the index of the stash to remove (0 being the most recent one). + public virtual StashApplyStatus Apply(int index) + { + return Apply(index, null); + } + + /// + /// Pops a single stashed state from the stash list + /// + /// the index of the stash to remove (0 being the most recent one). + /// the options to use for checking out the stash. + public virtual StashApplyStatus Pop(int index, StashApplyOptions options) + { + if (index < 0) + { + throw new ArgumentException("The passed index must be a positive integer.", nameof(index)); + } + + if (options == null) + { + options = new StashApplyOptions(); + } + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options.CheckoutOptions ?? new CheckoutOptions())) + { + var opts = new GitStashApplyOpts + { + CheckoutOptions = checkoutOptionsWrapper.Options, + Flags = options.ApplyModifiers, + }; + + if (options.ProgressHandler != null) + { + opts.ApplyProgressCallback = (progress, payload) => options.ProgressHandler(progress) ? 0 : -1; + } + + return Proxy.git_stash_pop(repo.Handle, index, opts); + } + } + + /// + /// Pops a single stashed state from the stash list using the default options. + /// + /// the index of the stash to remove (0 being the most recent one). + public virtual StashApplyStatus Pop(int index) + { + return Pop(index, null); + } + /// /// Remove a single stashed state from the stash list. /// @@ -108,7 +231,7 @@ public virtual void Remove(int index) { if (index < 0) { - throw new ArgumentException("The passed index must be a positive integer.", "index"); + throw new ArgumentException("The passed index must be a positive integer.", nameof(index)); } Proxy.git_stash_drop(repo.Handle, index); @@ -118,8 +241,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/StatusEntry.cs b/LibGit2Sharp/StatusEntry.cs index ecf56dbee..bd2ef8883 100644 --- a/LibGit2Sharp/StatusEntry.cs +++ b/LibGit2Sharp/StatusEntry.cs @@ -58,7 +58,7 @@ public virtual RenameDetails HeadToIndexRenameDetails } /// - /// Gets the rename details from the Index to the working directory, if this contains + /// Gets the rename details from the Index to the working directory, if this contains /// public virtual RenameDetails IndexToWorkDirRenameDetails { @@ -66,10 +66,10 @@ public virtual RenameDetails IndexToWorkDirRenameDetails } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as StatusEntry); @@ -121,10 +121,11 @@ private string DebuggerDisplay get { if ((State & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex || - (State & FileStatus.RenamedInWorkDir) == FileStatus.RenamedInWorkDir) + (State & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) { - string oldFilePath = ((State & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex) ? - HeadToIndexRenameDetails.OldFilePath : IndexToWorkDirRenameDetails.OldFilePath; + string oldFilePath = ((State & FileStatus.RenamedInIndex) != 0) + ? HeadToIndexRenameDetails.OldFilePath + : IndexToWorkDirRenameDetails.OldFilePath; return string.Format(CultureInfo.InvariantCulture, "{0}: {1} -> {2}", State, oldFilePath, FilePath); } diff --git a/LibGit2Sharp/StatusOptions.cs b/LibGit2Sharp/StatusOptions.cs index 9c7528017..47dba9197 100644 --- a/LibGit2Sharp/StatusOptions.cs +++ b/LibGit2Sharp/StatusOptions.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace LibGit2Sharp +namespace LibGit2Sharp { /// /// Flags controlling what files are reported by status. @@ -40,6 +35,9 @@ public sealed class StatusOptions public StatusOptions() { DetectRenamesInIndex = true; + IncludeIgnored = true; + IncludeUntracked = true; + RecurseUntrackedDirs = true; } /// @@ -66,5 +64,46 @@ public StatusOptions() /// Recurse into ignored directories /// public bool RecurseIgnoredDirs { get; set; } + + /// + /// Recurse into untracked directories + /// + public bool RecurseUntrackedDirs { get; set; } + + /// + /// Limit the scope of paths to consider to the provided pathspecs + /// + /// + /// If a PathSpec is given, the results from rename detection may + /// not be accurate. + /// + public string[] PathSpec { get; set; } + + /// + /// When set to true, the PathSpec paths will be considered + /// as explicit paths, and NOT as pathspecs containing globs. + /// + public bool DisablePathSpecMatch { get; set; } + + /// + /// Include unaltered files when scanning for status + /// + /// + /// Unaltered meaning the file is identical in the working directory, the index and HEAD. + /// + public bool IncludeUnaltered { get; set; } + + /// + /// Include ignored files when scanning for status + /// + /// + /// ignored meaning present in .gitignore. Defaults to true for back compat but may improve perf to not include if you have thousands of ignored files. + /// + public bool IncludeIgnored { get; set; } + + /// + /// Include untracked files when scanning for status + /// + public bool IncludeUntracked { get; set; } } } diff --git a/LibGit2Sharp/Submodule.cs b/LibGit2Sharp/Submodule.cs index c15e224d0..f8193af13 100644 --- a/LibGit2Sharp/Submodule.cs +++ b/LibGit2Sharp/Submodule.cs @@ -21,7 +21,7 @@ public class Submodule : IEquatable, IBelongToARepository private readonly ILazy headCommitId; private readonly ILazy indexCommitId; private readonly ILazy workdirCommitId; - private readonly ILazy fetchRecurseSubmodulesRule; + private readonly ILazy fetchRecurseSubmodulesRule; private readonly ILazy ignoreRule; private readonly ILazy updateRule; @@ -46,7 +46,7 @@ internal Submodule(Repository repo, string name, string path, string url) var rules = new SubmoduleLazyGroup(repo, name); fetchRecurseSubmodulesRule = rules.AddLazy(Proxy.git_submodule_fetch_recurse_submodules); ignoreRule = rules.AddLazy(Proxy.git_submodule_ignore); - updateRule = rules.AddLazy(Proxy.git_submodule_update); + updateRule = rules.AddLazy(Proxy.git_submodule_update_strategy); } /// @@ -85,7 +85,7 @@ internal Submodule(Repository repo, string name, string path, string url) /// Note that at this time, LibGit2Sharp does not honor this setting and the /// fetch functionality current ignores submodules. /// - public virtual bool FetchRecurseSubmodulesRule { get { return fetchRecurseSubmodulesRule.Value; } } + public virtual SubmoduleRecurse FetchRecurseSubmodulesRule { get { return fetchRecurseSubmodulesRule.Value; } } /// /// The ignore rule of the submodule. @@ -98,22 +98,19 @@ internal Submodule(Repository repo, string name, string path, string url) public virtual SubmoduleUpdate UpdateRule { get { return updateRule.Value; } } /// - /// Retrieves the state of this submodule in the working directory compared to the staging area and the latest commmit. + /// Retrieves the state of this submodule in the working directory compared to the staging area and the latest commit. /// /// The of this submodule. public virtual SubmoduleStatus RetrieveStatus() { - using (var handle = Proxy.git_submodule_lookup(repo.Handle, Name)) - { - return Proxy.git_submodule_status(handle); - } + return Proxy.git_submodule_status(repo.Handle, Name); } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as Submodule); @@ -139,7 +136,7 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// /// The that represents the current . public override string ToString() @@ -151,8 +148,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} => {1}", Name, Url); + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", Name, Url); } } diff --git a/LibGit2Sharp/SubmoduleCollection.cs b/LibGit2Sharp/SubmoduleCollection.cs index 8713e3e4c..061196c7d 100644 --- a/LibGit2Sharp/SubmoduleCollection.cs +++ b/LibGit2Sharp/SubmoduleCollection.cs @@ -41,13 +41,76 @@ public virtual Submodule this[string name] { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - return Lookup(name, handle => - new Submodule(repo, name, - Proxy.git_submodule_path(handle), - Proxy.git_submodule_url(handle))); + return Lookup(name, handle => new Submodule(repo, name, + Proxy.git_submodule_path(handle), + Proxy.git_submodule_url(handle))); } } + /// + /// Initialize specified submodule. + /// + /// Existing entries in the config file for this submodule are not be + /// modified unless is true. + /// + /// + /// The name of the submodule to update. + /// Overwrite existing entries. + public virtual void Init(string name, bool overwrite) + { + using (var handle = Proxy.git_submodule_lookup(repo.Handle, name)) + { + if (handle == null) + { + throw new NotFoundException("Submodule lookup failed for '{0}'.", + name); + } + + Proxy.git_submodule_init(handle, overwrite); + } + } + + /// + /// Update specified submodule. + /// + /// This will: + /// 1) Optionally initialize the if it not already initialized, + /// 2) clone the sub repository if it has not already been cloned, and + /// 3) checkout the commit ID for the submodule in the sub repository. + /// + /// + /// The name of the submodule to update. + /// Options controlling submodule update behavior and callbacks. + public virtual void Update(string name, SubmoduleUpdateOptions options) + { + options ??= new SubmoduleUpdateOptions(); + + using var handle = Proxy.git_submodule_lookup(repo.Handle, name) ?? throw new NotFoundException("Submodule lookup failed for '{0}'.", name); + using var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options); + using var fetchOptionsWrapper = new GitFetchOptionsWrapper(); + + var gitCheckoutOptions = checkoutOptionsWrapper.Options; + + var gitFetchOptions = fetchOptionsWrapper.Options; + gitFetchOptions.ProxyOptions = options.FetchOptions.ProxyOptions.CreateGitProxyOptions(); + gitFetchOptions.RemoteCallbacks = new RemoteCallbacks(options.FetchOptions).GenerateCallbacks(); + + if (options.FetchOptions != null && options.FetchOptions.CustomHeaders != null) + { + gitFetchOptions.CustomHeaders = + GitStrArrayManaged.BuildFrom(options.FetchOptions.CustomHeaders); + } + + var gitSubmoduleUpdateOpts = new GitSubmoduleUpdateOptions + { + Version = 1, + CheckoutOptions = gitCheckoutOptions, + FetchOptions = gitFetchOptions + }; + + Proxy.git_submodule_update(handle, options.Init, ref gitSubmoduleUpdateOpts); + } + /// /// Returns an enumerator that iterates through the collection. /// @@ -70,17 +133,18 @@ IEnumerator IEnumerable.GetEnumerator() internal bool TryStage(string relativePath, bool writeIndex) { - return Lookup(relativePath, handle => - { - if (handle == null) - return false; - - Proxy.git_submodule_add_to_index(handle, writeIndex); - return true; - }); + return Lookup(relativePath, + handle => + { + if (handle == null) + return false; + + Proxy.git_submodule_add_to_index(handle, writeIndex); + return true; + }); } - internal T Lookup(string name, Func selector, bool throwIfNotFound = false) + internal T Lookup(string name, Func selector, bool throwIfNotFound = false) { using (var handle = Proxy.git_submodule_lookup(repo.Handle, name)) { @@ -92,9 +156,7 @@ internal T Lookup(string name, Func selector, bool th if (throwIfNotFound) { - throw new LibGit2SharpException(string.Format( - CultureInfo.InvariantCulture, - "Submodule lookup failed for '{0}'.", name)); + throw new LibGit2SharpException("Submodule lookup failed for '{0}'.", name); } return default(T); @@ -105,8 +167,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/SubmoduleRecurse.cs b/LibGit2Sharp/SubmoduleRecurse.cs new file mode 100644 index 000000000..7ad024c0d --- /dev/null +++ b/LibGit2Sharp/SubmoduleRecurse.cs @@ -0,0 +1,25 @@ +namespace LibGit2Sharp +{ + /// + /// Submodule recurse rule options. + /// + public enum SubmoduleRecurse + { + /// + /// Reset to the value in the config. + /// + Reset = -1, + /// + /// Do not recurse into submodules. + /// + No = 0, + /// + /// Recurse into submodules. + /// + Yes = 1, + /// + /// Recurse into submodules only when commit not already in local clone. + /// + OnDemand = 2, + } +} diff --git a/LibGit2Sharp/SubmoduleUpdate.cs b/LibGit2Sharp/SubmoduleUpdate.cs index 02f290bc8..45fad71d8 100644 --- a/LibGit2Sharp/SubmoduleUpdate.cs +++ b/LibGit2Sharp/SubmoduleUpdate.cs @@ -8,22 +8,27 @@ public enum SubmoduleUpdate /// /// Reset to the last saved update rule. /// - Default = -1, + Reset = -1, /// - /// Checkout the commit recorded in the superproject. + /// Only used when you don't want to specify any particular update + /// rule. /// - Checkout = 0, + Unspecified = 0, + /// + /// This is the default - checkout the commit recorded in the superproject. + /// + Checkout = 1, /// /// Rebase the current branch of the submodule onto the commit recorded in the superproject. /// - Rebase = 1, + Rebase = 2, /// /// Merge the commit recorded in the superproject into the current branch of the submodule. /// - Merge = 2, + Merge = 3, /// /// Do not update the submodule. /// - None = 3, + None = 4, } } diff --git a/LibGit2Sharp/SubmoduleUpdateOptions.cs b/LibGit2Sharp/SubmoduleUpdateOptions.cs new file mode 100644 index 000000000..082e17338 --- /dev/null +++ b/LibGit2Sharp/SubmoduleUpdateOptions.cs @@ -0,0 +1,53 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Options controlling Submodule Update behavior and callbacks. + /// + public sealed class SubmoduleUpdateOptions : IConvertableToGitCheckoutOpts + { + /// + /// Initialize the submodule if it is not already initialized. + /// + public bool Init { get; set; } + + /// + /// Delegate to be called during checkout for files that match + /// desired filter specified with the NotifyFlags property. + /// + public CheckoutNotifyHandler OnCheckoutNotify { get; set; } + + /// Delegate through which checkout will notify callers of + /// certain conditions. The conditions that are reported is + /// controlled with the CheckoutNotifyFlags property. + public CheckoutProgressHandler OnCheckoutProgress { get; set; } + + /// + /// The flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. + /// + public CheckoutNotifyFlags CheckoutNotifyFlags { get; set; } + + /// + /// Collection of parameters controlling Fetch behavior. + /// + public FetchOptions FetchOptions { get; internal set; } = new(); + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get { return CheckoutStrategy.GIT_CHECKOUT_SAFE; } + } + + CheckoutNotifyFlags IConvertableToGitCheckoutOpts.CheckoutNotifyFlags + { + get { return CheckoutNotifyFlags; } + } + } +} diff --git a/LibGit2Sharp/SymbolicReference.cs b/LibGit2Sharp/SymbolicReference.cs index 5bb475312..4615389e7 100644 --- a/LibGit2Sharp/SymbolicReference.cs +++ b/LibGit2Sharp/SymbolicReference.cs @@ -45,9 +45,12 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0} => {1} => \"{2}\"", - CanonicalName, TargetIdentifier, - (Target != null) ? Target.TargetIdentifier : "?"); + "{0} => {1} => \"{2}\"", + CanonicalName, + TargetIdentifier, + (Target != null) + ? Target.TargetIdentifier + : "?"); } } } diff --git a/LibGit2Sharp/Tag.cs b/LibGit2Sharp/Tag.cs index 0cc8cb09e..cb2436346 100644 --- a/LibGit2Sharp/Tag.cs +++ b/LibGit2Sharp/Tag.cs @@ -13,8 +13,7 @@ protected Tag() internal Tag(Repository repo, Reference reference, string canonicalName) : base(repo, reference, _ => canonicalName) - { - } + { } /// /// Gets the optional information associated to this tag. @@ -40,6 +39,27 @@ public virtual GitObject Target } } + /// + /// Gets the peeled that this tag points to. + /// + public virtual GitObject PeeledTarget + { + get + { + GitObject target = TargetObject; + + var annotation = target as TagAnnotation; + + while (annotation != null) + { + target = annotation.Target; + annotation = target as TagAnnotation; + } + + return target; + } + } + /// /// Indicates whether the tag holds any metadata. /// diff --git a/LibGit2Sharp/TagAnnotation.cs b/LibGit2Sharp/TagAnnotation.cs index 3028cfc16..66a84d556 100644 --- a/LibGit2Sharp/TagAnnotation.cs +++ b/LibGit2Sharp/TagAnnotation.cs @@ -23,8 +23,12 @@ internal TagAnnotation(Repository repo, ObjectId id) : base(repo, id) { lazyName = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_tag_name); - lazyTarget = GitObjectLazyGroup.Singleton(repo, id, - obj => BuildFrom(repo, Proxy.git_tag_target_id(obj), Proxy.git_tag_target_type(obj), null)); + lazyTarget = GitObjectLazyGroup.Singleton(repo, + id, + obj => BuildFrom(repo, + Proxy.git_tag_target_id(obj), + Proxy.git_tag_target_type(obj), + null)); group = new GitObjectLazyGroup(repo, id); lazyTagger = group.AddLazy(Proxy.git_tag_tagger); diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs index 04c426095..98bfd257d 100644 --- a/LibGit2Sharp/TagCollection.cs +++ b/LibGit2Sharp/TagCollection.cs @@ -69,6 +69,73 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Creates an annotated tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// The tagger. + /// The message. + public virtual Tag Add(string name, string objectish, Signature tagger, string message) + { + return Add(name, objectish, tagger, message, false); + } + + /// + /// Creates an annotated tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// The tagger. + /// The message. + /// True to allow silent overwriting a potentially existing tag, false otherwise. + public virtual Tag Add(string name, string objectish, Signature tagger, string message, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(objectish, "target"); + + GitObject objectToTag = repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); + + return Add(name, objectToTag, tagger, message, allowOverwrite); + } + + /// + /// Creates a lightweight tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + public virtual Tag Add(string name, string objectish) + { + return Add(name, objectish, false); + } + + /// + /// Creates a lightweight tag with the specified name. + /// + /// The name. + /// Revparse spec for the target object. + /// True to allow silent overwriting a potentially existing tag, false otherwise. + public virtual Tag Add(string name, string objectish, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(objectish, "objectish"); + + GitObject objectToTag = repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); + + return Add(name, objectToTag, allowOverwrite); + } + + /// + /// Creates an annotated tag with the specified name. + /// + /// The name. + /// The target . + /// The tagger. + /// The message. + /// The added . + public virtual Tag Add(string name, GitObject target, Signature tagger, string message) + { + return Add(name, target, tagger, message, false); + } + /// /// Creates an annotated tag with the specified name. /// @@ -78,7 +145,7 @@ IEnumerator IEnumerable.GetEnumerator() /// The message. /// True to allow silent overwriting a potentially existing tag, false otherwise. /// The added . - public virtual Tag Add(string name, GitObject target, Signature tagger, string message, bool allowOverwrite = false) + public virtual Tag Add(string name, GitObject target, Signature tagger, string message, bool allowOverwrite) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(target, "target"); @@ -92,6 +159,17 @@ public virtual Tag Add(string name, GitObject target, Signature tagger, string m return this[name]; } + /// + /// Creates a lightweight tag with the specified name. + /// + /// The name. + /// The target . + /// The added . + public virtual Tag Add(string name, GitObject target) + { + return Add(name, target, false); + } + /// /// Creates a lightweight tag with the specified name. /// @@ -99,7 +177,7 @@ public virtual Tag Add(string name, GitObject target, Signature tagger, string m /// The target . /// True to allow silent overwriting a potentially existing tag, false otherwise. /// The added . - public virtual Tag Add(string name, GitObject target, bool allowOverwrite = false) + public virtual Tag Add(string name, GitObject target, bool allowOverwrite) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(target, "target"); @@ -109,6 +187,17 @@ public virtual Tag Add(string name, GitObject target, bool allowOverwrite = fals return this[name]; } + /// + /// Deletes the tag with the specified name. + /// + /// The short or canonical name of the tag to delete. + public virtual void Remove(string name) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + Proxy.git_tag_delete(repo.Handle, UnCanonicalizeName(name)); + } + /// /// Deletes the tag with the specified name. /// @@ -117,7 +206,7 @@ public virtual void Remove(Tag tag) { Ensure.ArgumentNotNull(tag, "tag"); - this.Remove(tag.CanonicalName); + Remove(tag.CanonicalName); } private static string NormalizeToCanonicalName(string name) @@ -132,7 +221,7 @@ private static string NormalizeToCanonicalName(string name) return string.Concat(Reference.TagPrefix, name); } - internal static string UnCanonicalizeName(string name) + private static string UnCanonicalizeName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); @@ -148,8 +237,7 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "Count = {0}", this.Count()); + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); } } } diff --git a/LibGit2Sharp/TagCollectionExtensions.cs b/LibGit2Sharp/TagCollectionExtensions.cs deleted file mode 100644 index f976e65ef..000000000 --- a/LibGit2Sharp/TagCollectionExtensions.cs +++ /dev/null @@ -1,56 +0,0 @@ -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class TagCollectionExtensions - { - /// - /// Creates an annotated tag with the specified name. - /// - /// The name. - /// Revparse spec for the target object. - /// The tagger. - /// The message. - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish, Signature tagger, string message, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(objectish, "target"); - - GitObject objectToTag = tags.repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); - - return tags.Add(name, objectToTag, tagger, message, allowOverwrite); - } - - /// - /// Creates a lightweight tag with the specified name. - /// - /// The name. - /// Revparse spec for the target object. - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// The being worked with. - public static Tag Add(this TagCollection tags, string name, string objectish, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(objectish, "objectish"); - - GitObject objectToTag = tags.repo.Lookup(objectish, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); - - return tags.Add(name, objectToTag, allowOverwrite); - } - - /// - /// Deletes the tag with the specified name. - /// - /// The short or canonical name of the tag to delete. - /// The being worked with. - public static void Remove(this TagCollection tags, string name) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - - Proxy.git_tag_delete(tags.repo.Handle, TagCollection.UnCanonicalizeName(name)); - } - } -} diff --git a/LibGit2Sharp/TagFetchMode.cs b/LibGit2Sharp/TagFetchMode.cs index 8e8efc79f..993833f46 100644 --- a/LibGit2Sharp/TagFetchMode.cs +++ b/LibGit2Sharp/TagFetchMode.cs @@ -7,10 +7,16 @@ public enum TagFetchMode { /// - /// Default behavior. Will automatically retrieve tags that + /// Use the setting from the configuration + /// or, when there isn't any, fallback to default behavior. + /// + FromConfigurationOrDefault = 0, // GIT_REMOTE_DOWNLOAD_TAGS_FALLBACK + + /// + /// Will automatically retrieve tags that /// point to objects retrieved during this fetch. /// - Auto = 0, // GIT_REMOTE_DOWNLOAD_TAGS_AUTO + Auto, // GIT_REMOTE_DOWNLOAD_TAGS_AUTO /// /// No tag will be retrieved. diff --git a/LibGit2Sharp/TarArchiver.cs b/LibGit2Sharp/TarArchiver.cs index 0710734fb..3c9ecdd51 100644 --- a/LibGit2Sharp/TarArchiver.cs +++ b/LibGit2Sharp/TarArchiver.cs @@ -29,11 +29,23 @@ public override void BeforeArchiving(Tree tree, ObjectId oid, DateTimeOffset mod } // Store the sha in the pax_global_header - using (var stream = new MemoryStream(Encoding.ASCII.GetBytes( - string.Format(CultureInfo.InvariantCulture, "52 comment={0}\n", oid.Sha)))) + using (var stream = + new MemoryStream(Encoding.ASCII.GetBytes(string.Format(CultureInfo.InvariantCulture, + "52 comment={0}\n", + oid.Sha)))) { - writer.Write("pax_global_header", stream, modificationTime, "666".OctalToInt32(), - "0", "0", 'g', "root", "root", "0", "0", oid.Sha, false); + writer.Write("pax_global_header", + stream, modificationTime, + "666".OctalToInt32(), + "0", + "0", + 'g', + "root", + "root", + "0", + "0", + oid.Sha, + false); } } @@ -43,27 +55,55 @@ protected override void AddTreeEntry(string path, TreeEntry entry, DateTimeOffse { case Mode.GitLink: case Mode.Directory: - writer.Write(path + "/", null, modificationTime, "775".OctalToInt32(), - "0", "0", '5', "root", "root", "0", "0", entry.TargetId.Sha, false); + writer.Write(path + "/", + null, + modificationTime, + "775".OctalToInt32(), + "0", + "0", + '5', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + false); break; case Mode.ExecutableFile: case Mode.NonExecutableFile: case Mode.NonExecutableGroupWritableFile: - var blob = ((Blob) entry.Target); + var blob = ((Blob)entry.Target); - WriteStream(path, entry, modificationTime, - () => blob.IsBinary ? blob.GetContentStream() : blob.GetContentStream(new FilteringOptions(path))); + WriteStream(path, + entry, + modificationTime, + () => blob.IsBinary + ? blob.GetContentStream() + : blob.GetContentStream(new FilteringOptions(path))); break; case Mode.SymbolicLink: using (Stream contentStream = ((Blob)entry.Target).GetContentStream(new FilteringOptions(path))) { - writer.Write(path, contentStream, modificationTime, "777".OctalToInt32(), - "0", "0", '2', "root", "root", "0", "0", entry.TargetId.Sha, true); + writer.Write(path, + contentStream, + modificationTime, + "777".OctalToInt32(), + "0", + "0", + '2', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + true); } break; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Unsupported file mode: {0} (sha1: {1}).", entry.Mode, entry.TargetId.Sha)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Unsupported file mode: {0} (sha1: {1}).", + entry.Mode, + entry.TargetId.Sha)); } } @@ -71,9 +111,21 @@ private void WriteStream(string path, TreeEntry entry, DateTimeOffset modificati { using (Stream contentStream = streamer()) { - writer.Write(path, contentStream, modificationTime, - (entry.Mode == Mode.ExecutableFile) ? "775".OctalToInt32() : "664".OctalToInt32(), - "0", "0", '0', "root", "root", "0", "0", entry.TargetId.Sha, false); + writer.Write(path, + contentStream, + modificationTime, + (entry.Mode == Mode.ExecutableFile) + ? "775".OctalToInt32() + : "664".OctalToInt32(), + "0", + "0", + '0', + "root", + "root", + "0", + "0", + entry.TargetId.Sha, + false); } } diff --git a/LibGit2Sharp/TransferProgress.cs b/LibGit2Sharp/TransferProgress.cs index 4ef072a8a..984c1741e 100644 --- a/LibGit2Sharp/TransferProgress.cs +++ b/LibGit2Sharp/TransferProgress.cs @@ -1,10 +1,13 @@ -using LibGit2Sharp.Core; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; namespace LibGit2Sharp { /// /// Expose progress values from a fetch operation. /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TransferProgress { private GitTransferProgress gitTransferProgress; @@ -28,10 +31,7 @@ internal TransferProgress(GitTransferProgress gitTransferProgress) /// public virtual int TotalObjects { - get - { - return (int) gitTransferProgress.total_objects; - } + get { return (int)gitTransferProgress.total_objects; } } /// @@ -39,10 +39,7 @@ public virtual int TotalObjects /// public virtual int IndexedObjects { - get - { - return (int) gitTransferProgress.indexed_objects; - } + get { return (int)gitTransferProgress.indexed_objects; } } /// @@ -50,20 +47,26 @@ public virtual int IndexedObjects /// public virtual int ReceivedObjects { - get - { - return (int) gitTransferProgress.received_objects; - } + get { return (int)gitTransferProgress.received_objects; } } /// /// Number of bytes received. /// public virtual long ReceivedBytes + { + get { return (long)gitTransferProgress.received_bytes; } + } + + private string DebuggerDisplay { get { - return (long) gitTransferProgress.received_bytes; + return string.Format(CultureInfo.InvariantCulture, + "{0}/{1}, {2} bytes", + ReceivedObjects, + TotalObjects, + ReceivedBytes); } } } diff --git a/LibGit2Sharp/TransientIndex.cs b/LibGit2Sharp/TransientIndex.cs new file mode 100644 index 000000000..b62678c83 --- /dev/null +++ b/LibGit2Sharp/TransientIndex.cs @@ -0,0 +1,31 @@ +using System; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// An implementation of with disposal managed by the caller + /// (instead of automatically disposing when the repository is disposed) + /// + public class TransientIndex : Index, IDisposable + { + /// + /// Needed for mocking purposes. + /// + protected TransientIndex() + { } + + internal TransientIndex(IndexHandle handle, Repository repo) + : base(handle, repo) + { + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.Handle.SafeDispose(); + } + } +} diff --git a/LibGit2Sharp/Tree.cs b/LibGit2Sharp/Tree.cs index c70eb9ee1..30f534a99 100644 --- a/LibGit2Sharp/Tree.cs +++ b/LibGit2Sharp/Tree.cs @@ -5,16 +5,21 @@ using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; +using System.Text; +using System; namespace LibGit2Sharp { /// /// A container which references a list of other s and s. /// + /// + /// Since the introduction of partially cloned repositories, trees might be missing on your local repository (see https://git-scm.com/docs/partial-clone) + /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Tree : GitObject, IEnumerable { - private readonly FilePath path; + private readonly string path; private readonly ILazy lazyCount; @@ -24,69 +29,90 @@ public class Tree : GitObject, IEnumerable protected Tree() { } - internal Tree(Repository repo, ObjectId id, FilePath path) + internal Tree(Repository repo, ObjectId id, string path) : base(repo, id) { this.path = path ?? ""; - lazyCount = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_tree_entrycount); + lazyCount = GitObjectLazyGroup.Singleton(repo, id, Proxy.git_tree_entrycount, throwIfMissing: true); } /// /// Gets the number of immediately under this . /// - public virtual int Count { get { return lazyCount.Value; } } + /// Throws if tree is missing + public virtual int Count => lazyCount.Value; /// /// Gets the pointed at by the in this instance. /// /// The relative path to the from this instance. /// null if nothing has been found, the otherwise. + /// Throws if tree is missing public virtual TreeEntry this[string relativePath] { get { return RetrieveFromPath(relativePath); } } - private TreeEntry RetrieveFromPath(FilePath relativePath) + private unsafe TreeEntry RetrieveFromPath(string relativePath) { - if (relativePath.IsNullOrEmpty()) + if (string.IsNullOrEmpty(relativePath)) { return null; } - using (TreeEntrySafeHandle_Owned treeEntryPtr = Proxy.git_tree_entry_bypath(repo.Handle, Id, relativePath)) + using (TreeEntryHandle treeEntry = Proxy.git_tree_entry_bypath(repo.Handle, Id, relativePath)) { - if (treeEntryPtr == null) + if (treeEntry == null) { return null; } - string posixPath = relativePath.Posix; - string filename = posixPath.Split('/').Last(); - string parentPath = posixPath.Substring(0, posixPath.Length - filename.Length); - return new TreeEntry(treeEntryPtr, Id, repo, path.Combine(parentPath)); + string filename = relativePath.Split('/').Last(); + string parentPath = relativePath.Substring(0, relativePath.Length - filename.Length); + return new TreeEntry(treeEntry, Id, repo, Tree.CombinePath(path, parentPath)); } } - internal string Path + internal string Path => path; + + #region IEnumerable Members + + unsafe TreeEntry byIndex(ObjectSafeWrapper obj, uint i, ObjectId parentTreeId, Repository repo, string parentPath) { - get { return path.Native; } + using (var entryHandle = Proxy.git_tree_entry_byindex(obj.ObjectPtr, i)) + { + return new TreeEntry(entryHandle, parentTreeId, repo, parentPath); + } } - #region IEnumerable Members + internal static string CombinePath(string a, string b) + { + var bld = new StringBuilder(); + bld.Append(a); + if (!string.IsNullOrEmpty(a) && + !a.EndsWith("/", StringComparison.Ordinal) && + !b.StartsWith("/", StringComparison.Ordinal)) + { + bld.Append('/'); + } + bld.Append(b); + + return bld.ToString(); + } /// /// Returns an enumerator that iterates through the collection. /// /// An object that can be used to iterate through the collection. + /// Throws if tree is missing public virtual IEnumerator GetEnumerator() { - using (var obj = new ObjectSafeWrapper(Id, repo.Handle)) + using (var obj = new ObjectSafeWrapper(Id, repo.Handle, throwIfMissing: true)) { for (uint i = 0; i < Count; i++) { - TreeEntrySafeHandle handle = Proxy.git_tree_entry_byindex(obj.ObjectPtr, i); - yield return new TreeEntry(handle, Id, repo, path); + yield return byIndex(obj, i, Id, repo, path); } } } @@ -95,6 +121,7 @@ public virtual IEnumerator GetEnumerator() /// Returns an enumerator that iterates through the collection. /// /// An object that can be used to iterate through the collection. + /// Throws if tree is missing IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -107,7 +134,9 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "{0}, Count = {1}", Id.ToString(7), Count); + "{0}, Count = {1}", + Id.ToString(7), + Count); } } } diff --git a/LibGit2Sharp/TreeChanges.cs b/LibGit2Sharp/TreeChanges.cs index 1529a25cd..6a54d9c09 100644 --- a/LibGit2Sharp/TreeChanges.cs +++ b/LibGit2Sharp/TreeChanges.cs @@ -15,32 +15,10 @@ namespace LibGit2Sharp /// To obtain the actual patch of the diff, use the class when calling Compare.. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class TreeChanges : IEnumerable + public class TreeChanges : IEnumerable, IDiffResult { - private readonly IDictionary changes = new Dictionary(); - private readonly List added = new List(); - private readonly List deleted = new List(); - private readonly List modified = new List(); - private readonly List typeChanged = new List(); - private readonly List unmodified = new List(); - private readonly List renamed = new List(); - private readonly List copied = new List(); - - private readonly IDictionary> fileDispatcher = Build(); - - private static IDictionary> Build() - { - return new Dictionary> - { - { ChangeKind.Modified, (de, d) => de.modified.Add(d) }, - { ChangeKind.Deleted, (de, d) => de.deleted.Add(d) }, - { ChangeKind.Added, (de, d) => de.added.Add(d) }, - { ChangeKind.TypeChanged, (de, d) => de.typeChanged.Add(d) }, - { ChangeKind.Unmodified, (de, d) => de.unmodified.Add(d) }, - { ChangeKind.Renamed, (de, d) => de.renamed.Add(d) }, - { ChangeKind.Copied, (de, d) => de.copied.Add(d) }, - }; - } + private readonly DiffHandle diff; + private readonly Lazy count; /// /// Needed for mocking purposes. @@ -48,23 +26,47 @@ private static IDictionary> Bu protected TreeChanges() { } - internal TreeChanges(DiffSafeHandle diff) + internal unsafe TreeChanges(DiffHandle diff) { - Proxy.git_diff_foreach(diff, FileCallback, null, null); + this.diff = diff; + this.count = new Lazy(() => Proxy.git_diff_num_deltas(diff)); } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) + /// + /// Enumerates the diff and yields deltas with the specified change kind. + /// + /// Change type to filter on. + private IEnumerable GetChangesOfKind(ChangeKind changeKind) { - AddFileChange(delta); - return 0; + TreeEntryChanges entry; + for (int i = 0; i < Count; i++) + { + if (TryGetEntryWithChangeTypeAt(i, changeKind, out entry)) + { + yield return entry; + } + } } - private void AddFileChange(GitDiffDelta delta) + /// + /// This is method exists to work around .net not allowing unsafe code + /// in iterators. + /// + private unsafe bool TryGetEntryWithChangeTypeAt(int index, ChangeKind changeKind, out TreeEntryChanges entry) { - var treeEntryChanges = new TreeEntryChanges(delta); + if (index < 0 || index > count.Value) + throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); - fileDispatcher[treeEntryChanges.Status](this, treeEntryChanges); - changes.Add(treeEntryChanges.Path, treeEntryChanges); + var delta = Proxy.git_diff_get_delta(diff, index); + + if (TreeEntryChanges.GetStatusFromChangeKind(delta->status) == changeKind) + { + entry = new TreeEntryChanges(delta); + return true; + } + + entry = null; + return false; } #region IEnumerable Members @@ -75,48 +77,41 @@ private void AddFileChange(GitDiffDelta delta) /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return changes.Values.GetEnumerator(); + for (int i = 0; i < Count; i++) + { + yield return GetEntryAt(i); + } } /// - /// Returns an enumerator that iterates through the collection. + /// This is method exists to work around .net not allowing unsafe code + /// in iterators. /// - /// An object that can be used to iterate through the collection. - IEnumerator IEnumerable.GetEnumerator() + private unsafe TreeEntryChanges GetEntryAt(int index) { - return GetEnumerator(); - } + if (index < 0 || index > count.Value) + throw new ArgumentOutOfRangeException(nameof(index), "Index was out of range. Must be non-negative and less than the size of the collection."); - #endregion + return new TreeEntryChanges(Proxy.git_diff_get_delta(diff, index)); + } /// - /// Gets the corresponding to the specified . + /// Returns an enumerator that iterates through the collection. /// - public virtual TreeEntryChanges this[string path] + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() { - get { return this[(FilePath)path]; } + return GetEnumerator(); } - private TreeEntryChanges this[FilePath path] - { - get - { - TreeEntryChanges treeEntryChanges; - if (changes.TryGetValue(path, out treeEntryChanges)) - { - return treeEntryChanges; - } - - return null; - } - } + #endregion /// /// List of that have been been added. /// public virtual IEnumerable Added { - get { return added; } + get { return GetChangesOfKind(ChangeKind.Added); } } /// @@ -124,7 +119,7 @@ public virtual IEnumerable Added /// public virtual IEnumerable Deleted { - get { return deleted; } + get { return GetChangesOfKind(ChangeKind.Deleted); } } /// @@ -132,7 +127,7 @@ public virtual IEnumerable Deleted /// public virtual IEnumerable Modified { - get { return modified; } + get { return GetChangesOfKind(ChangeKind.Modified); } } /// @@ -140,7 +135,7 @@ public virtual IEnumerable Modified /// public virtual IEnumerable TypeChanged { - get { return typeChanged; } + get { return GetChangesOfKind(ChangeKind.TypeChanged); } } /// @@ -148,7 +143,7 @@ public virtual IEnumerable TypeChanged /// public virtual IEnumerable Renamed { - get { return renamed; } + get { return GetChangesOfKind(ChangeKind.Renamed); } } /// @@ -156,7 +151,7 @@ public virtual IEnumerable Renamed /// public virtual IEnumerable Copied { - get { return copied; } + get { return GetChangesOfKind(ChangeKind.Copied); } } /// @@ -164,7 +159,23 @@ public virtual IEnumerable Copied /// public virtual IEnumerable Unmodified { - get { return unmodified; } + get { return GetChangesOfKind(ChangeKind.Unmodified); } + } + + /// + /// List of which are conflicted + /// + public virtual IEnumerable Conflicted + { + get { return GetChangesOfKind(ChangeKind.Conflicted); } + } + + /// + /// Gets the number of in this comparison. + /// + public virtual int Count + { + get { return count.Value; } } private string DebuggerDisplay @@ -172,10 +183,32 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "+{0} ~{1} -{2} \u00B1{3} R{4} C{5}", - Added.Count(), Modified.Count(), Deleted.Count(), - TypeChanged.Count(), Renamed.Count(), Copied.Count()); + "+{0} ~{1} -{2} \u00B1{3} R{4} C{5}", + Added.Count(), + Modified.Count(), + Deleted.Count(), + TypeChanged.Count(), + Renamed.Count(), + Copied.Count()); } } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected virtual void Dispose(bool disposing) + { + diff.SafeDispose(); + } } } diff --git a/LibGit2Sharp/TreeDefinition.cs b/LibGit2Sharp/TreeDefinition.cs index bedd07851..91389f6e3 100644 --- a/LibGit2Sharp/TreeDefinition.cs +++ b/LibGit2Sharp/TreeDefinition.cs @@ -57,6 +57,24 @@ private void AddEntry(string targetTreeEntryName, TreeEntryDefinition treeEntryD entries.Add(targetTreeEntryName, treeEntryDefinition); } + /// + /// Removes the located at each of the + /// specified . + /// + /// The paths within this . + /// The current . + public virtual TreeDefinition Remove(IEnumerable treeEntryPaths) + { + Ensure.ArgumentNotNull(treeEntryPaths, "treeEntryPaths"); + + foreach (var treeEntryPath in treeEntryPaths) + { + Remove(treeEntryPath); + } + + return this; + } + /// /// Removes a located the specified path. /// @@ -76,6 +94,7 @@ public virtual TreeDefinition Remove(string treeEntryPath) if (segments.Item2 == null) { entries.Remove(segments.Item1); + unwrappedTrees.Remove(segments.Item1); } if (!unwrappedTrees.ContainsKey(segments.Item1)) @@ -108,17 +127,15 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntryDefinitio Ensure.ArgumentNotNullOrEmptyString(targetTreeEntryPath, "targetTreeEntryPath"); Ensure.ArgumentNotNull(treeEntryDefinition, "treeEntryDefinition"); - if (Path.IsPathRooted(targetTreeEntryPath)) - { - throw new ArgumentException("The provided path is an absolute path."); - } - if (treeEntryDefinition is TransientTreeTreeEntryDefinition) { throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, - "The {0} references a target which hasn't been created in the {1} yet. " + - "This situation can occur when the target is a whole new {2} being created, or when an existing {2} is being updated because some of its children were added/removed.", - typeof(TreeEntryDefinition).Name, typeof(ObjectDatabase).Name, typeof(Tree).Name)); + "The {0} references a target which hasn't been created in the {1} yet. " + + "This situation can occur when the target is a whole new {2} being created, " + + "or when an existing {2} is being updated because some of its children were added/removed.", + typeof(TreeEntryDefinition).Name, + typeof(ObjectDatabase).Name, + typeof(Tree).Name)); } Tuple segments = ExtractPosixLeadingSegment(targetTreeEntryPath); @@ -185,6 +202,23 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, string filePath, M return Add(targetTreeEntryPath, ted); } + /// + /// Adds or replaces a from an existing blob specified by its Object ID at the specified location. + /// + /// The path within this . + /// The object ID for this entry. + /// The file related attributes. + /// The current . + public virtual TreeDefinition Add(string targetTreeEntryPath, ObjectId id, Mode mode) + { + Ensure.ArgumentNotNull(id, "id"); + Ensure.ArgumentConformsTo(mode, m => m.HasAny(TreeEntryDefinition.BlobModes), "mode"); + + TreeEntryDefinition ted = TreeEntryDefinition.From(id, mode); + + return Add(targetTreeEntryPath, ted); + } + /// /// Adds or replaces a , dynamically built from the provided , at the specified location. /// @@ -283,7 +317,7 @@ internal Tree Build(Repository repository) { WrapAllTreeDefinitions(repository); - using (var builder = new TreeBuilder()) + using (var builder = new TreeBuilder(repository)) { var builtTreeEntryDefinitions = new List>(entries.Count); @@ -309,8 +343,13 @@ internal Tree Build(Repository repository) builtTreeEntryDefinitions.ForEach(t => entries[t.Item1] = t.Item2); - ObjectId treeId = builder.Write(repository); - return repository.Lookup(treeId); + ObjectId treeId = builder.Write(); + var result = repository.Lookup(treeId); + if (result == null) + { + throw new LibGit2SharpException("Unable to read created tree"); + } + return result; } } @@ -347,7 +386,9 @@ public virtual TreeEntryDefinition this[string treeEntryPath] if (segments.Item2 != null) { TreeDefinition td = RetrieveOrBuildTreeDefinition(segments.Item1, false); - return td == null ? null : td[segments.Item2]; + return td == null + ? null + : td[segments.Item2]; } TreeEntryDefinition treeEntryDefinition; @@ -355,9 +396,9 @@ public virtual TreeEntryDefinition this[string treeEntryPath] } } - private static Tuple ExtractPosixLeadingSegment(FilePath targetPath) + private static Tuple ExtractPosixLeadingSegment(string targetPath) { - string[] segments = targetPath.Posix.Split(new[] { '/' }, 2); + string[] segments = targetPath.Split(new[] { '/' }, 2); if (segments[0] == string.Empty || (segments.Length == 2 && (segments[1] == string.Empty || segments[1].StartsWith("/", StringComparison.Ordinal)))) { @@ -369,11 +410,11 @@ private static Tuple ExtractPosixLeadingSegment(FilePath targetP private class TreeBuilder : IDisposable { - private readonly TreeBuilderSafeHandle handle; + private readonly TreeBuilderHandle handle; - public TreeBuilder() + public TreeBuilder(Repository repo) { - handle = Proxy.git_treebuilder_create(); + handle = Proxy.git_treebuilder_new(repo.Handle); } public void Insert(string name, TreeEntryDefinition treeEntryDefinition) @@ -381,9 +422,9 @@ public void Insert(string name, TreeEntryDefinition treeEntryDefinition) Proxy.git_treebuilder_insert(handle, name, treeEntryDefinition); } - public ObjectId Write(Repository repo) + public ObjectId Write() { - return Proxy.git_treebuilder_write(repo.Handle, handle); + return Proxy.git_treebuilder_write(handle); } public void Dispose() diff --git a/LibGit2Sharp/TreeDefinitionExtensions.cs b/LibGit2Sharp/TreeDefinitionExtensions.cs deleted file mode 100644 index 4ff8c62cb..000000000 --- a/LibGit2Sharp/TreeDefinitionExtensions.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class TreeDefinitionExtensions - { - /// - /// Removes the located at each of the - /// specified . - /// - /// The . - /// The paths within this . - /// The current . - public static TreeDefinition Remove(this TreeDefinition td, IEnumerable treeEntryPaths) - { - Ensure.ArgumentNotNull(td, "td"); - Ensure.ArgumentNotNull(treeEntryPaths, "treeEntryPaths"); - - foreach (var treeEntryPath in treeEntryPaths) - { - td.Remove(treeEntryPath); - } - - return td; - } - } -} diff --git a/LibGit2Sharp/TreeEntry.cs b/LibGit2Sharp/TreeEntry.cs index 54dd95998..943e14570 100644 --- a/LibGit2Sharp/TreeEntry.cs +++ b/LibGit2Sharp/TreeEntry.cs @@ -1,13 +1,15 @@ using System; +using System.Diagnostics; using System.Globalization; -using System.Runtime.InteropServices; using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// /// Representation of an entry in a . /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TreeEntry : IEquatable { private readonly ObjectId parentTreeId; @@ -25,20 +27,20 @@ public class TreeEntry : IEquatable protected TreeEntry() { } - internal TreeEntry(SafeHandle obj, ObjectId parentTreeId, Repository repo, FilePath parentPath) + internal unsafe TreeEntry(TreeEntryHandle entry, ObjectId parentTreeId, Repository repo, string parentPath) { this.parentTreeId = parentTreeId; this.repo = repo; - targetOid = Proxy.git_tree_entry_id(obj); + targetOid = Proxy.git_tree_entry_id(entry); - GitObjectType treeEntryTargetType = Proxy.git_tree_entry_type(obj); + GitObjectType treeEntryTargetType = Proxy.git_tree_entry_type(entry); TargetType = treeEntryTargetType.ToTreeEntryTargetType(); target = new Lazy(RetrieveTreeEntryTarget); - Mode = Proxy.git_tree_entry_attributes(obj); - Name = Proxy.git_tree_entry_name(obj); - path = new Lazy(() => System.IO.Path.Combine(parentPath.Native, Name)); + Mode = Proxy.git_tree_entry_attributes(entry); + Name = Proxy.git_tree_entry_name(entry); + path = new Lazy(() => Tree.CombinePath(parentPath, Name)); } /// @@ -84,18 +86,17 @@ private GitObject RetrieveTreeEntryTarget() return GitObject.BuildFrom(repo, targetOid, TargetType.ToGitObjectType(), Path); default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, - "TreeEntry target of type '{0}' is not supported.", - TargetType)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "TreeEntry target of type '{0}' is not supported.", + TargetType)); } } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as TreeEntry); @@ -141,5 +142,16 @@ public override int GetHashCode() { return !Equals(left, right); } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "TreeEntry: {0} => {1}", + Path, + TargetId); + } + } } } diff --git a/LibGit2Sharp/TreeEntryChanges.cs b/LibGit2Sharp/TreeEntryChanges.cs index 205ff42c3..1ab1a6172 100644 --- a/LibGit2Sharp/TreeEntryChanges.cs +++ b/LibGit2Sharp/TreeEntryChanges.cs @@ -16,19 +16,35 @@ public class TreeEntryChanges protected TreeEntryChanges() { } - internal TreeEntryChanges(GitDiffDelta delta) + internal unsafe TreeEntryChanges(git_diff_delta* delta) { - Path = LaxFilePathMarshaler.FromNative(delta.NewFile.Path).Native; - OldPath = LaxFilePathMarshaler.FromNative(delta.OldFile.Path).Native; + Path = LaxUtf8Marshaler.FromNative(delta->new_file.Path); + OldPath = LaxUtf8Marshaler.FromNative(delta->old_file.Path); - Mode = (Mode)delta.NewFile.Mode; - OldMode = (Mode)delta.OldFile.Mode; - Oid = delta.NewFile.Id; - OldOid = delta.OldFile.Id; + Mode = (Mode)delta->new_file.Mode; + OldMode = (Mode)delta->old_file.Mode; + Oid = ObjectId.BuildFromPtr(&delta->new_file.Id); + OldOid = ObjectId.BuildFromPtr(&delta->old_file.Id); + Exists = (delta->new_file.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; + OldExists = (delta->old_file.Flags & GitDiffFlags.GIT_DIFF_FLAG_EXISTS) != 0; - Status = (delta.Status == ChangeKind.Untracked || delta.Status == ChangeKind.Ignored) - ? ChangeKind.Added - : delta.Status; + Status = GetStatusFromChangeKind(delta->status); + } + + // This treatment of change kind was apparently introduced in order to be able + // to compare a tree against the index, see commit fdc972b. It's extracted + // here so that TreeEntry can use the same rules without having to instantiate + // a TreeEntryChanges object. + internal static ChangeKind GetStatusFromChangeKind(ChangeKind changeKind) + { + switch (changeKind) + { + case ChangeKind.Untracked: + case ChangeKind.Ignored: + return ChangeKind.Added; + default: + return changeKind; + } } /// @@ -46,6 +62,17 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId Oid { get; private set; } + /// + /// The file exists in the new side of the diff. + /// This is useful in determining if you have content in + /// the ours or theirs side of a conflict. This will + /// be false during a conflict that deletes both the + /// "ours" and "theirs" sides, or when the diff is a + /// delete and the status is + /// . + /// + public virtual bool Exists { get; private set; } + /// /// The kind of change that has been done (added, deleted, modified ...). /// @@ -66,13 +93,26 @@ internal TreeEntryChanges(GitDiffDelta delta) /// public virtual ObjectId OldOid { get; private set; } + /// + /// The file exists in the old side of the diff. + /// This is useful in determining if you have an ancestor + /// side to a conflict. This will be false during a + /// conflict that involves both the "ours" and "theirs" + /// side being added, or when the diff is an add and the + /// status is . + /// + public virtual bool OldExists { get; private set; } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "Path = {0}, File {1}", - !string.IsNullOrEmpty(Path) ? Path : OldPath, Status); + "Path = {0}, File {1}", + !string.IsNullOrEmpty(Path) + ? Path + : OldPath, + Status); } } } diff --git a/LibGit2Sharp/TreeEntryDefinition.cs b/LibGit2Sharp/TreeEntryDefinition.cs index c0eb979d3..d32cc722c 100644 --- a/LibGit2Sharp/TreeEntryDefinition.cs +++ b/LibGit2Sharp/TreeEntryDefinition.cs @@ -19,8 +19,7 @@ public class TreeEntryDefinition : IEquatable /// Needed for mocking purposes. /// protected TreeEntryDefinition() - { - } + { } /// /// Gets file mode. @@ -45,23 +44,38 @@ internal virtual GitObject Target internal static TreeEntryDefinition From(TreeEntry treeEntry) { return new TreeEntryDefinition - { - Mode = treeEntry.Mode, - TargetType = treeEntry.TargetType, - TargetId = treeEntry.TargetId, - target = new Lazy(() => treeEntry.Target) - }; + { + Mode = treeEntry.Mode, + TargetType = treeEntry.TargetType, + TargetId = treeEntry.TargetId, + target = new Lazy(() => treeEntry.Target) + }; } internal static TreeEntryDefinition From(Blob blob, Mode mode) { + Ensure.ArgumentNotNull(blob, "blob"); + + return new TreeEntryDefinition + { + Mode = mode, + TargetType = TreeEntryTargetType.Blob, + TargetId = blob.Id, + target = new Lazy(() => blob) + }; + } + + internal static TreeEntryDefinition From(ObjectId id, Mode mode) + { + Ensure.ArgumentNotNull(id, "id"); + Ensure.ArgumentNotNull(mode, "mode"); + return new TreeEntryDefinition - { - Mode = mode, - TargetType = TreeEntryTargetType.Blob, - TargetId = blob.Id, - target = new Lazy(() => blob) - }; + { + Mode = mode, + TargetType = TreeEntryTargetType.Blob, + TargetId = id + }; } internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode) @@ -69,39 +83,39 @@ internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode Ensure.ArgumentConformsTo(mode, m => m.HasAny(BlobModes), "mode"); return new TransientBlobTreeEntryDefinition - { - Builder = odb => odb.CreateBlob(filePath), - Mode = mode, - }; + { + Builder = odb => odb.CreateBlob(filePath), + Mode = mode, + }; } internal static TreeEntryDefinition From(ObjectId objectId) { return new TreeEntryDefinition - { - Mode = Mode.GitLink, - TargetType = TreeEntryTargetType.GitLink, - TargetId = objectId, - target = new Lazy(() => { throw new InvalidOperationException("Shouldn't be necessary."); }), - }; + { + Mode = Mode.GitLink, + TargetType = TreeEntryTargetType.GitLink, + TargetId = objectId, + target = new Lazy(() => { throw new InvalidOperationException("Shouldn't be necessary."); }), + }; } internal static TreeEntryDefinition From(Tree tree) { return new TreeEntryDefinition - { - Mode = Mode.Directory, - TargetType = TreeEntryTargetType.Tree, - TargetId = tree.Id, - target = new Lazy(() => tree) - }; + { + Mode = Mode.Directory, + TargetType = TreeEntryTargetType.Tree, + TargetId = tree.Id, + target = new Lazy(() => tree) + }; } /// - /// Determines whether the specified is equal to the current . + /// Determines whether the specified is equal to the current . /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. public override bool Equals(object obj) { return Equals(obj as TreeEntryDefinition); diff --git a/LibGit2Sharp/TreeEntryTargetType.cs b/LibGit2Sharp/TreeEntryTargetType.cs index a4e54d73a..181708ec7 100644 --- a/LibGit2Sharp/TreeEntryTargetType.cs +++ b/LibGit2Sharp/TreeEntryTargetType.cs @@ -38,8 +38,9 @@ public static GitObjectType ToGitObjectType(this TreeEntryTargetType type) return GitObjectType.Blob; default: - throw new InvalidOperationException( - string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a GitObjectType.", type)); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a GitObjectType.", + type)); } } } diff --git a/LibGit2Sharp/UnbornBranchException.cs b/LibGit2Sharp/UnbornBranchException.cs index 2704d1a93..8f01a63ab 100644 --- a/LibGit2Sharp/UnbornBranchException.cs +++ b/LibGit2Sharp/UnbornBranchException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif namespace LibGit2Sharp { @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// The exception that is thrown when a operation requiring an existing /// branch is performed against an unborn branch. /// +#if NETFRAMEWORK [Serializable] +#endif public class UnbornBranchException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public UnbornBranchException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public UnbornBranchException() /// A message that describes the error. public UnbornBranchException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnbornBranchException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public UnbornBranchException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnbornBranchException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +54,7 @@ public UnbornBranchException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnbornBranchException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/UniqueIdentifier.targets b/LibGit2Sharp/UniqueIdentifier.targets deleted file mode 100644 index f6eb926e3..000000000 --- a/LibGit2Sharp/UniqueIdentifier.targets +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - . - $(MSBuildThisFileDirectory) - $(LibGit2SharpPath)\Core\UniqueIdentifier.cs - $(CoreCompileDependsOn);GenerateUniqueIdentifierCs - $(CoreCleanDependsOn);CleanUniqueIdentifierCs - - - - - - - - - - - diff --git a/LibGit2Sharp/UnmatchedPathException.cs b/LibGit2Sharp/UnmatchedPathException.cs index 0227e9bcf..96e5654c7 100644 --- a/LibGit2Sharp/UnmatchedPathException.cs +++ b/LibGit2Sharp/UnmatchedPathException.cs @@ -1,21 +1,23 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; -using LibGit2Sharp.Core; +#endif namespace LibGit2Sharp { /// /// The exception that is thrown when an explicit path or a list of explicit paths could not be matched. /// +#if NETFRAMEWORK [Serializable] +#endif public class UnmatchedPathException : LibGit2SharpException { /// /// Initializes a new instance of the class. /// public UnmatchedPathException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +25,16 @@ public UnmatchedPathException() /// A message that describes the error. public UnmatchedPathException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnmatchedPathException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +43,9 @@ public UnmatchedPathException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnmatchedPathException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,7 +53,7 @@ public UnmatchedPathException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnmatchedPathException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif } } diff --git a/LibGit2Sharp/UnmergedIndexEntriesException.cs b/LibGit2Sharp/UnmergedIndexEntriesException.cs index f221b4a61..f9f1a834b 100644 --- a/LibGit2Sharp/UnmergedIndexEntriesException.cs +++ b/LibGit2Sharp/UnmergedIndexEntriesException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -8,15 +10,16 @@ namespace LibGit2Sharp /// The exception that is thrown when an operation that requires a fully merged index /// is performed against an index with unmerged entries /// +#if NETFRAMEWORK [Serializable] - public class UnmergedIndexEntriesException : LibGit2SharpException +#endif + public class UnmergedIndexEntriesException : NativeException { /// /// Initializes a new instance of the class. /// public UnmergedIndexEntriesException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -24,8 +27,16 @@ public UnmergedIndexEntriesException() /// A message that describes the error. public UnmergedIndexEntriesException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UnmergedIndexEntriesException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -34,9 +45,9 @@ public UnmergedIndexEntriesException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UnmergedIndexEntriesException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -44,12 +55,19 @@ public UnmergedIndexEntriesException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UnmergedIndexEntriesException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal UnmergedIndexEntriesException(string message, GitErrorCategory category) + : base(message, category) + { } - internal UnmergedIndexEntriesException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.UnmergedEntries; + } } } } diff --git a/LibGit2Sharp/UserCanceledException.cs b/LibGit2Sharp/UserCanceledException.cs index fdc03396e..f3c6af7dd 100644 --- a/LibGit2Sharp/UserCanceledException.cs +++ b/LibGit2Sharp/UserCanceledException.cs @@ -1,5 +1,7 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,15 +9,16 @@ namespace LibGit2Sharp /// /// The exception that is thrown when an operation is canceled. /// +#if NETFRAMEWORK [Serializable] - public class UserCancelledException : LibGit2SharpException +#endif + public class UserCancelledException : NativeException { /// /// Initializes a new instance of the class. /// public UserCancelledException() - { - } + { } /// /// Initializes a new instance of the class with a specified error message. @@ -23,8 +26,16 @@ public UserCancelledException() /// A message that describes the error. public UserCancelledException(string message) : base(message) - { - } + { } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// A composite format string for use in . + /// An object array that contains zero or more objects to format. + public UserCancelledException(string format, params object[] args) + : base(format, args) + { } /// /// Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. @@ -33,9 +44,9 @@ public UserCancelledException(string message) /// The exception that is the cause of the current exception. If the parameter is not a null reference, the current exception is raised in a catch block that handles the inner exception. public UserCancelledException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// /// Initializes a new instance of the class with a serialized data. /// @@ -43,12 +54,19 @@ public UserCancelledException(string message, Exception innerException) /// The that contains contextual information about the source or destination. protected UserCancelledException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + { } +#endif + + internal UserCancelledException(string message, GitErrorCategory category) + : base(message, category) + { } - internal UserCancelledException(string message, GitErrorCode code, GitErrorCategory category) - : base(message, code, category) + internal override GitErrorCode ErrorCode { + get + { + return GitErrorCode.User; + } } } } diff --git a/LibGit2Sharp/UsernamePasswordCredentials.cs b/LibGit2Sharp/UsernamePasswordCredentials.cs index 4f61fa0ac..761be5c74 100644 --- a/LibGit2Sharp/UsernamePasswordCredentials.cs +++ b/LibGit2Sharp/UsernamePasswordCredentials.cs @@ -1,4 +1,6 @@ using System; +using System.Text; +using System.Runtime.InteropServices; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -12,12 +14,8 @@ public sealed class UsernamePasswordCredentials : Credentials /// Callback to acquire a credential object. /// /// The newly created credential object. - /// The resource for which we are demanding a credential. - /// The username that was embedded in a "user@host" - /// A bitmask stating which cred types are OK to return. - /// The payload provided when specifying this callback. /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. - protected internal override int GitCredentialHandler(out IntPtr cred, IntPtr url, IntPtr usernameFromUrl, GitCredentialType types, IntPtr payload) + protected internal override int GitCredentialHandler(out IntPtr cred) { if (Username == null || Password == null) { @@ -27,6 +25,15 @@ protected internal override int GitCredentialHandler(out IntPtr cred, IntPtr url return NativeMethods.git_cred_userpass_plaintext_new(out cred, Username, Password); } + static internal unsafe UsernamePasswordCredentials FromNative(GitCredentialUserpass* gitCred) + { + return new UsernamePasswordCredentials() + { + Username = LaxUtf8Marshaler.FromNative(gitCred->username), + Password = LaxUtf8Marshaler.FromNative(gitCred->password), + }; + } + /// /// Username for username/password authentication (as in HTTP basic auth). /// diff --git a/LibGit2Sharp/Version.cs b/LibGit2Sharp/Version.cs index f519c3a17..2c21ccad2 100644 --- a/LibGit2Sharp/Version.cs +++ b/LibGit2Sharp/Version.cs @@ -1,5 +1,4 @@ using System.Globalization; -using System.IO; using System.Reflection; using LibGit2Sharp.Core; @@ -10,8 +9,6 @@ namespace LibGit2Sharp /// public class Version { - private readonly Assembly assembly = typeof(Repository).Assembly; - /// /// Needed for mocking purposes. /// @@ -24,14 +21,14 @@ internal static Version Build() } /// - /// Returns the of the - /// the LibGit2Sharp library. + /// Returns version of the LibGit2Sharp library. /// - public virtual System.Version MajorMinorPatch + public virtual string InformationalVersion { get { - return assembly.GetName().Version; + var attribute = Assembly.GetExecutingAssembly().GetCustomAttribute(); + return attribute.InformationalVersion; } } @@ -42,32 +39,23 @@ public virtual System.Version MajorMinorPatch /// A enumeration. public virtual BuiltInFeatures Features { - get - { - return Proxy.git_libgit2_features(); - } + get { return Proxy.git_libgit2_features(); } } /// - /// Returns the SHA hash for the libgit2 library. + /// Returns the SHA hash for the libgit2 library. /// - public virtual string LibGit2CommitSha - { - get - { - return ReadContentFromResource(assembly, "libgit2_hash.txt").Substring(0, 7); - } - } + public virtual string LibGit2CommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2CommitSha); /// /// Returns the SHA hash for the LibGit2Sharp library. /// - public virtual string LibGit2SharpCommitSha + public virtual string LibGit2SharpCommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2SharpCommitSha); + + private string RetrieveAbbrevShaFrom(string sha) { - get - { - return ReadContentFromResource(assembly, "libgit2sharp_hash.txt").Substring(0, 7); - } + var index = sha.Length > 7 ? 7 : sha.Length; + return sha.Substring(0, index); } /// @@ -75,7 +63,7 @@ public virtual string LibGit2SharpCommitSha /// /// /// The format of the version number is as follows: - /// Major.Minor.Patch-LibGit2Sharp_abbrev_hash-libgit2_abbrev_hash (x86|amd64 - features) + /// Major.Minor.Patch[-previewTag]+libgit2-{libgit2_abbrev_hash}.{LibGit2Sharp_hash} (arch - features) /// /// public override string ToString() @@ -87,23 +75,11 @@ private string RetrieveVersion() { string features = Features.ToString(); - return string.Format( - CultureInfo.InvariantCulture, - "{0}-{1}-{2} ({3} - {4})", - MajorMinorPatch.ToString(3), - LibGit2SharpCommitSha, - LibGit2CommitSha, - NativeMethods.ProcessorArchitecture, - features); - } - - private string ReadContentFromResource(Assembly assembly, string partialResourceName) - { - string name = string.Format(CultureInfo.InvariantCulture, "LibGit2Sharp.{0}", partialResourceName); - using (var sr = new StreamReader(assembly.GetManifestResourceStream(name))) - { - return sr.ReadLine(); - } + return string.Format(CultureInfo.InvariantCulture, + "{0} ({1} - {2})", + InformationalVersion, + Platform.ProcessorArchitecture, + features); } } } diff --git a/LibGit2Sharp/Worktree.cs b/LibGit2Sharp/Worktree.cs new file mode 100644 index 000000000..ca7f5ef16 --- /dev/null +++ b/LibGit2Sharp/Worktree.cs @@ -0,0 +1,139 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// A Worktree. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class Worktree : IEquatable, IBelongToARepository + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.Name); + + private readonly Repository parent; + //private readonly Repository worktree; + private readonly string name; + private WorktreeLock worktreeLock; + + /// + /// Needed for mocking purposes. + /// + protected Worktree() + { } + + internal Worktree(Repository repo, string name, WorktreeLock worktreeLock) + { + this.parent = repo; + this.name = name; + this.worktreeLock = worktreeLock; + } + + /// + /// + /// + /// + internal WorktreeHandle GetWorktreeHandle() + { + return Proxy.git_worktree_lookup(parent.Handle, name); + } + + /// + /// The name of the worktree. + /// + public virtual string Name { get { return name; } } + + /// + /// The Repository representation of the worktree + /// + public virtual Repository WorktreeRepository { get { return new Repository(GetWorktreeHandle()); } } + + /// + /// A flag indicating if the worktree is locked or not. + /// + public virtual bool IsLocked { get { return worktreeLock == null ? false : worktreeLock.IsLocked; } } + + /// + /// Gets the reason associated with the lock + /// + public virtual string LockReason { get { return worktreeLock == null ? null : worktreeLock.Reason; } } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public override bool Equals(object obj) + { + return Equals(obj as Worktree); + } + + /// + /// Determines whether the specified is equal to the current . + /// + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public bool Equals(Worktree other) + { + return equalityHelper.Equals(this, other); + } + + /// + /// Unlock the worktree + /// + public virtual void Unlock() + { + using (var handle = GetWorktreeHandle()) + { + Proxy.git_worktree_unlock(handle); + this.worktreeLock = Proxy.git_worktree_is_locked(handle); + } + } + + /// + /// Lock the worktree + /// + public virtual void Lock(string reason) + { + using (var handle = GetWorktreeHandle()) + { + Proxy.git_worktree_lock(handle, reason); + this.worktreeLock = Proxy.git_worktree_is_locked(handle); + } + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return equalityHelper.GetHashCode(this); + } + + /// + /// Returns the , a representation of the current . + /// + /// The that represents the current . + public override string ToString() + { + return Name; + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", Name, worktreeLock); + } + } + + IRepository IBelongToARepository.Repository { get { return parent; } } + } +} diff --git a/LibGit2Sharp/WorktreeCollection.cs b/LibGit2Sharp/WorktreeCollection.cs new file mode 100644 index 000000000..d99e11d7a --- /dev/null +++ b/LibGit2Sharp/WorktreeCollection.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The collection of worktrees in a + /// + public class WorktreeCollection : IEnumerable + { + internal readonly Repository repo; + + /// + /// Needed for mocking purposes. + /// + protected WorktreeCollection() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The repo. + internal WorktreeCollection(Repository repo) + { + this.repo = repo; + } + + /// + /// Gets the with the specified name. + /// + public virtual Worktree this[string name] + { + get + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + return Lookup(name, handle => new Worktree(repo, + name, + Proxy.git_worktree_is_locked(handle))); + } + } + + /// + /// Creates a worktree. + /// + /// The committish to checkout into the new worktree. + /// Name of the worktree. + /// Location of the worktree. + /// + public virtual Worktree Add(string committishOrBranchSpec, string name, string path, bool isLocked) + { + if (string.Equals(committishOrBranchSpec, name)) + { + // Proxy.git_worktree_add() creates a new branch of name = name, so if we want to checkout a given branch then the 'name' cannot be the same as the target branch + return null; + } + + var options = new git_worktree_add_options + { + version = 1, + locked = Convert.ToInt32(isLocked) + }; + + using (var handle = Proxy.git_worktree_add(repo.Handle, name, path, options)) + { + var worktree = new Worktree( + repo, + name, + Proxy.git_worktree_is_locked(handle)); + + // switch the worktree to the target branch + using (var repository = worktree.WorktreeRepository) + { + Commands.Checkout(repository, committishOrBranchSpec); + } + } + + return this[name]; + } + + /// + /// Creates a worktree. + /// + /// Name of the worktree. + /// Location of the worktree. + /// + public virtual Worktree Add(string name, string path, bool isLocked) + { + var options = new git_worktree_add_options + { + version = 1, + locked = Convert.ToInt32(isLocked) + }; + + using (var handle = Proxy.git_worktree_add(repo.Handle, name, path, options)) + { + return new Worktree( + repo, + name, + Proxy.git_worktree_is_locked(handle)); + } + } + + /// + /// + /// + /// + /// + public virtual bool Prune(Worktree worktree) + { + return Prune(worktree, false); + } + + /// + /// + /// + /// + /// + /// + public virtual bool Prune(Worktree worktree, bool ifLocked) + { + using (var handle = worktree.GetWorktreeHandle()) + { + git_worktree_prune_options options = new git_worktree_prune_options + { + version = 1, + // default + flags = GitWorktreePruneOptionFlags.GIT_WORKTREE_PRUNE_WORKING_TREE | GitWorktreePruneOptionFlags.GIT_WORKTREE_PRUNE_VALID + }; + + if (ifLocked) + { + options.flags |= GitWorktreePruneOptionFlags.GIT_WORKTREE_PRUNE_LOCKED; + } + + return Proxy.git_worktree_prune(handle, options); + } + } + + internal T Lookup(string name, Func selector, bool throwIfNotFound = false) + { + using (var handle = Proxy.git_worktree_lookup(repo.Handle, name)) + { + if (handle != null && Proxy.git_worktree_validate(handle)) + { + return selector(handle); + } + + if (throwIfNotFound) + { + throw new LibGit2SharpException("Worktree lookup failed for '{0}'.", name); + } + + return default(T); + } + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return Proxy.git_worktree_list(repo.Handle) + .Select(n => Lookup(n, handle => new Worktree(repo, n, Proxy.git_worktree_is_locked(handle)))) + .GetEnumerator(); + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); + } + } + } +} diff --git a/LibGit2Sharp/WorktreeLock.cs b/LibGit2Sharp/WorktreeLock.cs new file mode 100644 index 000000000..4ae5d799f --- /dev/null +++ b/LibGit2Sharp/WorktreeLock.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; + +namespace LibGit2Sharp +{ + /// + /// Represents the lock state of a Worktree + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class WorktreeLock + { + /// + /// Creates a new instance of with default, unlocked, state + /// + public WorktreeLock() : this(false, null) + { + + } + + /// + /// Creates a new instance of + /// + /// the locked state + /// the reason given for the lock + public WorktreeLock(bool isLocked, string reason) + { + IsLocked = isLocked; + Reason = reason; + } + /// + /// Gets a flag indicating if the worktree is locked + /// + public virtual bool IsLocked { get; } + + /// + /// Gets the reason, if set, for the lock + /// + public virtual string Reason { get; } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "{0} => {1}", IsLocked, Reason); + } + } + } +} diff --git a/LibGit2Sharp/libgit2_hash.txt b/LibGit2Sharp/libgit2_hash.txt deleted file mode 100644 index 953bb57b1..000000000 --- a/LibGit2Sharp/libgit2_hash.txt +++ /dev/null @@ -1 +0,0 @@ -e0383fa35f981c656043976a43c61bff059cb709 diff --git a/LibGit2Sharp/libgit2sharp_hash.txt b/LibGit2Sharp/libgit2sharp_hash.txt deleted file mode 100644 index 354664565..000000000 --- a/LibGit2Sharp/libgit2sharp_hash.txt +++ /dev/null @@ -1 +0,0 @@ -unknown diff --git a/NativeLibraryLoadTestApp/TestApp.cs b/NativeLibraryLoadTestApp/TestApp.cs new file mode 100644 index 000000000..6a9f3ab60 --- /dev/null +++ b/NativeLibraryLoadTestApp/TestApp.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; + +namespace LibGit2Sharp.Tests +{ + public class TestApp + { + [DllImport("kernel32")] + private static extern IntPtr GetModuleHandle(string path); + + [DllImport("kernel32")] + private static extern int GetModuleFileName(IntPtr handle, [Out] StringBuilder path, int size); + + static int Main(string[] args) + { + if (args.Length < 1 || args.Length > 2) + { + Console.Error.WriteLine("Usage: "); + return -1; + } + + var moduleName = args[0]; + var loadFromDirectory = args[1]; + var expectedPath = Path.Combine(loadFromDirectory, moduleName + ".dll"); + + GlobalSettings.NativeLibraryPath = loadFromDirectory; + var isValid = Repository.IsValid(Path.GetTempPath()); + + var capacity = ushort.MaxValue; + var moduleHandle = GetModuleHandle(moduleName); + var buffer = new StringBuilder(capacity); + int actualLength = GetModuleFileName(moduleHandle, buffer, capacity); + var actualPath = buffer.ToString(0, actualLength); + + if (expectedPath != actualPath) + { + Console.WriteLine(actualPath); + return 1; + } + + return 0; + } + } +} diff --git a/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj b/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj new file mode 100644 index 000000000..3bca18b34 --- /dev/null +++ b/NativeLibraryLoadTestApp/x64/NativeLibraryLoadTestApp.x64.csproj @@ -0,0 +1,17 @@ + + + + Exe + net472 + x64 + + + + + + + + + + + diff --git a/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj b/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj new file mode 100644 index 000000000..0596f203c --- /dev/null +++ b/NativeLibraryLoadTestApp/x86/NativeLibraryLoadTestApp.x86.csproj @@ -0,0 +1,17 @@ + + + + Exe + net472 + x86 + + + + + + + + + + + diff --git a/README.md b/README.md index a288b2829..3aafdceb1 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,46 @@ # LibGit2Sharp -**LibGit2Sharp brings all the might and speed of [libgit2][0], a native Git implementation, to the managed world of .Net and Mono.** +[![CI](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml/badge.svg)](https://github.com/libgit2/libgit2sharp/actions/workflows/ci.yml) +[![NuGet version (LibGit2Sharp)](https://img.shields.io/nuget/v/LibGit2Sharp.svg)](https://www.nuget.org/packages/LibGit2Sharp/) - [0]: http://libgit2.github.com/ +**LibGit2Sharp brings all the might and speed of [libgit2](http://libgit2.github.com/), a native Git implementation, to the managed world of .NET** ## Online resources - - [NuGet package][1] - - [Source code][2] - - [Continuous integration][3] - - [1]: http://nuget.org/List/Packages/LibGit2Sharp - [2]: https://github.com/libgit2/libgit2sharp/ - [3]: http://teamcity.codebetter.com/project.html?projectId=project127&guest=1 +- [NuGet package](http://nuget.org/List/Packages/LibGit2Sharp) +- [Source code](https://github.com/libgit2/libgit2sharp/) ## Troubleshooting and support - - Usage or programming related question? Post it on [StackOverflow][4] using the tag *libgit2sharp* - - Found a bug or missing a feature? Feed the [issue tracker][5] - - Announcements and related miscellanea through Twitter ([@libgit2sharp][6]) - - [4]: http://stackoverflow.com/questions/tagged/libgit2sharp - [5]: https://github.com/libgit2/libgit2sharp/issues - [6]: http://twitter.com/libgit2sharp - -## Current project build status -The [build][3] is generously hosted and run on the [CodeBetter TeamCity][7] infrastructure. - -| | Status of last build | -| :------ | :------: | -| **master** | [![master][8]][9] | -| **vNext (Win x86)** | [![vNext x86][10]][11] | -| **vNext (Win amd64)** | [![vNext amd64][12]][13] | -| **vNext (Mono)** | [![vNext Mono][14]][15] | - - [7]: http://codebetter.com/codebetter-ci/ - [8]: http://teamcity.codebetter.com/app/rest/builds/buildType:(id:bt398)/statusIcon - [9]: http://teamcity.codebetter.com/viewType.html?buildTypeId=bt398&guest=1 - [10]: http://teamcity.codebetter.com/app/rest/builds/buildType:(id:bt651)/statusIcon - [11]: http://teamcity.codebetter.com/viewType.html?buildTypeId=bt651&guest=1 - [12]: http://teamcity.codebetter.com/app/rest/builds/buildType:(id:bt652)/statusIcon - [13]: http://teamcity.codebetter.com/viewType.html?buildTypeId=bt652&guest=1 - [14]: http://teamcity.codebetter.com/app/rest/builds/buildType:(id:bt656)/statusIcon - [15]: http://teamcity.codebetter.com/viewType.html?buildTypeId=bt656&guest=1 +- Usage or programming related question? Post it on [StackOverflow](http://stackoverflow.com/questions/tagged/libgit2sharp) using the tag *libgit2sharp* +- Found a bug or missing a feature? Feed the [issue tracker](https://github.com/libgit2/libgit2sharp/issues) +- Announcements and related miscellanea through Twitter ([@libgit2sharp](http://twitter.com/libgit2sharp)) ## Quick contributing guide - - Fork and clone locally - - Create a topic specific branch. Add some nice feature. Do not forget the tests ;-) - - Send a Pull Request to spread the fun! +- Fork and clone locally +- Create a topic specific branch. Add some nice feature. Do not forget the tests ;-) +- Send a Pull Request to spread the fun! -More thorough information available in the [wiki][16]. +More thorough information is available in the [wiki](https://github.com/libgit2/libgit2sharp/wiki). - [16]: https://github.com/libgit2/libgit2sharp/wiki +## Optimizing unit testing -## Authors +LibGit2Sharp strives to have a comprehensive and robust unit test suite to ensure the quality of the software and to assist new contributors and users, who can use the tests as examples to jump start development. There are over one thousand unit tests for LibGit2Sharp, and this number will only grow as functionality is added. - - **Code:** The LibGit2Sharp [contributors][17] - - **Logo:** [Jason "blackant" Long][18] +You can do a few things to optimize running unit tests on Windows: - [17]: https://github.com/libgit2/libgit2sharp/contributors - [18]: https://github.com/jasonlong +1. Set the `LibGit2TestPath` environment variable to a path in your development environment. + * If the unit test framework cannot find the specified folder at runtime, it will fall back to the default location. +2. Configure your anti-virus software to ignore the `LibGit2TestPath` path. +3. Install a RAM disk like [IMDisk](http://www.ltr-data.se/opencode.html/#ImDisk) and set `LibGit2TestPath` to use it. + * Use `imdisk.exe -a -s 512M -m X: -p "/fs:fat /q /v:ramdisk /y"` to create a RAM disk. This command requires elevated privileges and can be placed into a scheduled task or run manually before you begin unit-testing. -## License +## Authors + +- **Code:** The LibGit2Sharp [contributors](https://github.com/libgit2/libgit2sharp/contributors) +- **Logo:** [Jason "blackant" Long](https://github.com/jasonlong) -The MIT license (Refer to the [LICENSE.md][19] file) +## License - [19]: https://github.com/libgit2/libgit2sharp/blob/master/LICENSE.md +The MIT license (Refer to the [LICENSE.md](https://github.com/libgit2/libgit2sharp/blob/master/LICENSE.md) file) diff --git a/Targets/CodeGenerator.targets b/Targets/CodeGenerator.targets new file mode 100644 index 000000000..16eb8f05b --- /dev/null +++ b/Targets/CodeGenerator.targets @@ -0,0 +1,65 @@ + + + + + $(IntermediateOutputPath)SourceRevisionId.txt + $(IntermediateOutputPath)UniqueIdentifier.g.cs + $(IntermediateOutputPath)AssemblyCommitIds.g.cs + + + + + + + + + + + + + $(SourceRevisionId) + $([System.Guid]::NewGuid()) + + namespace LibGit2Sharp.Core + { + internal static class UniqueId + { + public const string UniqueIdentifier = "$(UniqueIdentifier)"%3b + } + } + + + + + + + + + + + + + + $(SourceRevisionId) + unknown + + namespace LibGit2Sharp + { + internal static class AssemblyCommitIds + { + public const string LibGit2CommitSha = "$(libgit2_hash)"%3b + public const string LibGit2SharpCommitSha = "$(LibGit2SharpCommitSha)"%3b + } + } + + + + + + + + + + + + diff --git a/Targets/GenerateNativeDllName.targets b/Targets/GenerateNativeDllName.targets new file mode 100644 index 000000000..c74dcd31e --- /dev/null +++ b/Targets/GenerateNativeDllName.targets @@ -0,0 +1,30 @@ + + + + + $(IntermediateOutputPath)NativeDllName.g.cs + + + + + + + namespace LibGit2Sharp.Core + { + internal static class NativeDllName + { + public const string Name = "$(libgit2_filename)"%3b + } + } + + + + + + + + + + + + diff --git a/TrimmingTestApp/Program.cs b/TrimmingTestApp/Program.cs new file mode 100644 index 000000000..e568c227b --- /dev/null +++ b/TrimmingTestApp/Program.cs @@ -0,0 +1,3 @@ +using LibGit2Sharp; + +_ = new Repository(); diff --git a/TrimmingTestApp/TrimmingTestApp.csproj b/TrimmingTestApp/TrimmingTestApp.csproj new file mode 100644 index 000000000..3c6d341f6 --- /dev/null +++ b/TrimmingTestApp/TrimmingTestApp.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + Exe + enable + enable + true + true + true + + + + + + + + diff --git a/UpdateLibgit2ToSha.ps1 b/UpdateLibgit2ToSha.ps1 deleted file mode 100644 index cc46e05f6..000000000 --- a/UpdateLibgit2ToSha.ps1 +++ /dev/null @@ -1,212 +0,0 @@ -<# -.SYNOPSIS - Builds a version of libgit2 and copies it to Lib/NativeBinaries. -.PARAMETER sha - Desired libgit2 version. This is run through `git rev-parse`, so branch names are okay too. -.PARAMETER vs - Version of Visual Studio project files to generate. Cmake supports "10" (default), "11" and "12". -.PARAMETER libgit2Name - The base name (i.e without the file extension) of the libgit2 DLL to generate. Default is to use git2-$suffix, where $suffix is the first 7 characters of the SHA1 of the corresponding libgi2 commit as the suffix. -.PARAMETER test - If set, run the libgit2 tests on the desired version. -.PARAMETER debug - If set, build the "Debug" configuration of libgit2, rather than "RelWithDebInfo" (default). -#> - -Param( - [string]$sha = 'HEAD', - [string]$vs = '10', - [string]$libgit2Name = '', - [switch]$test, - [switch]$debug -) - -Set-StrictMode -Version Latest - -$self = Split-Path -Leaf $MyInvocation.MyCommand.Path -$libgit2sharpDirectory = Split-Path $MyInvocation.MyCommand.Path -$libgit2Directory = Join-Path $libgit2sharpDirectory "libgit2" -$x86Directory = Join-Path $libgit2sharpDirectory "Lib\NativeBinaries\x86" -$x64Directory = Join-Path $libgit2sharpDirectory "Lib\NativeBinaries\amd64" - -$build_clar = 'OFF' -if ($test.IsPresent) { $build_clar = 'ON' } -$configuration = "RelWithDebInfo" -if ($debug.IsPresent) { $configuration = "Debug" } - -function Run-Command([scriptblock]$Command, [switch]$Fatal, [switch]$Quiet) { - $output = "" - if ($Quiet) { - $output = & $Command 2>&1 - } else { - & $Command - } - - if (!$Fatal) { - return - } - - $exitCode = 0 - if ($LastExitCode -ne 0) { - $exitCode = $LastExitCode - } elseif (!$?) { - $exitCode = 1 - } else { - return - } - - $error = "``$Command`` failed" - if ($output) { - Write-Host -ForegroundColor yellow $output - $error += ". See output above." - } - Throw $error -} - -function Find-CMake { - # Look for cmake.exe in $Env:PATH. - $cmake = @(Get-Command cmake.exe)[0] 2>$null - if ($cmake) { - $cmake = $cmake.Definition - } else { - # Look for the highest-versioned cmake.exe in its default location. - $cmake = @(Resolve-Path (Join-Path ${Env:ProgramFiles(x86)} "CMake *\bin\cmake.exe")) - if ($cmake) { - $cmake = $cmake[-1].Path - } - } - if (!$cmake) { - throw "Error: Can't find cmake.exe" - } - $cmake -} - -function Find-Git { - $git = @(Get-Command git)[0] 2>$null - if ($git) { - $git = $git.Definition - Write-Host -ForegroundColor Gray "Using git: $git" - & $git --version | write-host -ForegroundColor Gray - return $git - } - throw "Error: Can't find git" -} - -Push-Location $libgit2Directory - -function Ensure-Property($expected, $propertyValue, $propertyName, $path) { - if ($propertyValue -eq $expected) { - return - } - - throw "Error: Invalid '$propertyName' property in generated '$path' (Expected: $expected - Actual: $propertyValue)" -} - -function Assert-Consistent-Naming($expected, $path) { - $dll = get-item $path - - Ensure-Property $expected $dll.Name "Name" $dll.Fullname - Ensure-Property $expected $dll.VersionInfo.InternalName "VersionInfo.InternalName" $dll.Fullname - Ensure-Property $expected $dll.VersionInfo.OriginalFilename "VersionInfo.OriginalFilename" $dll.Fullname -} - -& { - trap { - Pop-Location - break - } - - $cmake = Find-CMake - $ctest = Join-Path (Split-Path -Parent $cmake) "ctest.exe" - $git = Find-Git - - Write-Output "Fetching..." - Run-Command -Quiet { & $git fetch } - - Write-Output "Verifying $sha..." - $sha = & $git rev-parse $sha - if ($LASTEXITCODE -ne 0) { - write-host -foregroundcolor red "Error: invalid SHA. USAGE: $self " - popd - break - } - - if(![string]::IsNullOrEmpty($libgit2Name)) { - $binaryFilename = $libgit2Name - } else { - $binaryFilename = "git2-" + $sha.Substring(0,7) - } - - Write-Output "Checking out $sha..." - Run-Command -Quiet -Fatal { & $git checkout $sha } - - Write-Output "Building 32-bit..." - Run-Command -Quiet { & remove-item build -recurse -force } - Run-Command -Quiet { & mkdir build } - cd build - Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs" -D THREADSAFE=ON -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON .. } - Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } - if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } - cd $configuration - Assert-Consistent-Naming "$binaryFilename.dll" "*.dll" - Run-Command -Quiet { & rm *.exp } - Run-Command -Quiet { & rm $x86Directory\* } - Run-Command -Quiet -Fatal { & copy -fo * $x86Directory } - - Write-Output "Building 64-bit..." - cd .. - Run-Command -Quiet { & mkdir build64 } - cd build64 - Run-Command -Quiet -Fatal { & $cmake -G "Visual Studio $vs Win64" -D THREADSAFE=ON -D ENABLE_TRACE=ON -D "BUILD_CLAR=$build_clar" -D "LIBGIT2_FILENAME=$binaryFilename" -DSTDCALL=ON ../.. } - Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } - if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } - cd $configuration - Assert-Consistent-Naming "$binaryFilename.dll" "*.dll" - Run-Command -Quiet { & rm *.exp } - Run-Command -Quiet { & rm $x64Directory\* } - Run-Command -Quiet -Fatal { & copy -fo * $x64Directory } - - pop-location - - $dllNameClass = @" -namespace LibGit2Sharp.Core -{ - internal static class NativeDllName - { - public const string Name = "$binaryFilename"; - } -} -"@ - - sc -Encoding ASCII (Join-Path $libgit2sharpDirectory "Libgit2sharp\Core\NativeDllName.cs") $dllNameClass - sc -Encoding ASCII (Join-Path $libgit2sharpDirectory "Libgit2sharp\libgit2_hash.txt") $sha - - $buildProperties = @" - - - - - NativeBinaries\amd64\$binaryFilename.dll - PreserveNewest - - - NativeBinaries\amd64\$binaryFilename.pdb - PreserveNewest - - - NativeBinaries\x86\$binaryFilename.dll - PreserveNewest - - - NativeBinaries\x86\$binaryFilename.pdb - PreserveNewest - - - -"@ - - sc -Encoding UTF8 (Join-Path $libgit2sharpDirectory "nuget.package\build\LibGit2Sharp.props") $buildProperties - - Write-Output "Done!" -} -exit diff --git a/build.libgit2sharp.cmd b/build.libgit2sharp.cmd deleted file mode 100644 index 54ecc1a26..000000000 --- a/build.libgit2sharp.cmd +++ /dev/null @@ -1,12 +0,0 @@ -SETLOCAL - -SET BASEDIR=%~dp0 -SET FrameworkVersion=v4.0.30319 -SET FrameworkDir=%SystemRoot%\Microsoft.NET\Framework -SET CommitSha=%~1 - -"%FrameworkDir%\%FrameworkVersion%\msbuild.exe" "%BASEDIR%CI\build.msbuild" /property:CommitSha=%CommitSha% - -ENDLOCAL - -EXIT /B %ERRORLEVEL% \ No newline at end of file diff --git a/build.libgit2sharp.sh b/build.libgit2sharp.sh deleted file mode 100755 index 9e4b5da25..000000000 --- a/build.libgit2sharp.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -LIBGIT2SHA=`cat ./LibGit2Sharp/libgit2_hash.txt` -SHORTSHA=${LIBGIT2SHA:0:7} - -rm -rf libgit2/build -mkdir libgit2/build -pushd libgit2/build -export _BINPATH=`pwd` - -cmake -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo \ - -DTHREADSAFE:BOOL=ON \ - -DBUILD_CLAR:BOOL=OFF \ - -DUSE_SSH=OFF \ - -DENABLE_TRACE=ON \ - -DLIBGIT2_FILENAME=git2-$SHORTSHA \ - -DCMAKE_OSX_ARCHITECTURES="i386;x86_64" \ - .. -cmake --build . - -export LD_LIBRARY_PATH=$_BINPATH:$LD_LIBRARY_PATH -export DYLD_LIBRARY_PATH=$_BINPATH:$DYLD_LIBRARY_PATH - -popd - -export MONO_OPTIONS=--debug - -echo $DYLD_LIBRARY_PATH -echo $LD_LIBRARY_PATH -xbuild CI/build.msbuild /t:Deploy - -exit $? diff --git a/build.libgit2sharp.x64.cmd b/build.libgit2sharp.x64.cmd deleted file mode 100644 index 41702104b..000000000 --- a/build.libgit2sharp.x64.cmd +++ /dev/null @@ -1,12 +0,0 @@ -SETLOCAL - -SET BASEDIR=%~dp0 -SET FrameworkVersion=v4.0.30319 -SET FrameworkDir=%SystemRoot%\Microsoft.NET\Framework64 -SET CommitSha=%~1 - -"%FrameworkDir%\%FrameworkVersion%\msbuild.exe" "%BASEDIR%CI\build.msbuild" /property:CommitSha=%CommitSha% - -ENDLOCAL - -EXIT /B %ERRORLEVEL% \ No newline at end of file diff --git a/libgit2 b/libgit2 deleted file mode 160000 index e0383fa35..000000000 --- a/libgit2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0383fa35f981c656043976a43c61bff059cb709 diff --git a/libgit2sharp.snk b/libgit2sharp.snk new file mode 100644 index 000000000..01235f654 Binary files /dev/null and b/libgit2sharp.snk differ diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..35696f810 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/nuget.package/LibGit2Sharp.nuspec b/nuget.package/LibGit2Sharp.nuspec deleted file mode 100644 index c13128255..000000000 --- a/nuget.package/LibGit2Sharp.nuspec +++ /dev/null @@ -1,28 +0,0 @@ - - - - $id$ - $version$ - $author$ - nulltoken - https://github.com/libgit2/libgit2sharp/raw/master/LICENSE.md - https://github.com/libgit2/libgit2sharp/ - false - $description$ - https://github.com/libgit2/libgit2sharp/blob/master/CHANGES.md#libgit2sharp-changes - https://github.com/libgit2/libgit2sharp/raw/master/square-logo.png - libgit2 git wrapper bindings API dvcs vcs - - - - - - - - - - - - - - diff --git a/nuget.package/build.nuget.package.cmd b/nuget.package/build.nuget.package.cmd deleted file mode 100644 index 6def1d6d7..000000000 --- a/nuget.package/build.nuget.package.cmd +++ /dev/null @@ -1,31 +0,0 @@ -SETLOCAL -SET BASEDIR=%~dp0 -SET SRCDIR=%BASEDIR%..\LibGit2Sharp\ -SET CommitSha=%~1 - -IF "%CommitSha%" == "" ( - ECHO "Please provide the Libgit2Sharp commit Sha this package is being built from." - EXIT /B 1 -) - -REM the nuspec file needs to be next to the csproj, so copy it there during the pack operation -COPY "%BASEDIR%LibGit2Sharp.nuspec" "%SRCDIR%" - -PUSHD "%BASEDIR%" - -DEL *.nupkg - -CMD /c "..\build.libgit2sharp.cmd %CommitSha%" - -IF %ERRORLEVEL% NEQ 0 GOTO EXIT - -"..\Lib\NuGet\NuGet.exe" Pack -Symbols "%SRCDIR%LibGit2Sharp.csproj" -Prop Configuration=Release - -:EXIT -DEL "%SRCDIR%LibGit2Sharp.nuspec" - -ENDLOCAL -POPD - -PAUSE -EXIT /B %ERRORLEVEL% diff --git a/nuget.package/build/LibGit2Sharp.props b/nuget.package/build/LibGit2Sharp.props deleted file mode 100644 index 975cc521b..000000000 --- a/nuget.package/build/LibGit2Sharp.props +++ /dev/null @@ -1,21 +0,0 @@ - - - - - NativeBinaries\amd64\git2-e0383fa.dll - PreserveNewest - - - NativeBinaries\amd64\git2-e0383fa.pdb - PreserveNewest - - - NativeBinaries\x86\git2-e0383fa.dll - PreserveNewest - - - NativeBinaries\x86\git2-e0383fa.pdb - PreserveNewest - - -