diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..2e54d0f2d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,18 @@ +; Check http://editorconfig.org/ for more informations +; Top-most EditorConfig file +root = true + +; 4-column space indentation +[*] +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true +insert_final_newline = true + +; Not change VS generated files +[*.{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 6c668f7cc..32e17b4d0 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ Thumbs.db *_p.c *.ncb *.suo +.vs/ *.sln.ide/ *.tlb *.tlh @@ -34,4 +35,8 @@ _ReSharper*/ *.pidb *.userprefs *.swp -*.DotSettings \ No newline at end of file +*.DotSettings + +_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/.mailmap b/.mailmap index a1afe82d2..ef9348df1 100644 --- a/.mailmap +++ b/.mailmap @@ -3,3 +3,6 @@ Jameson Miller Ben Straub Saaman nulltoken +Martin Woodward +Carlos Martín Nieto +someoneigna diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b8571d1e2..000000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -# Travis-CI Build for libgit2sharp -# see travis-ci.org for details - -language: c - -# Make sure CMake is installed -install: - - sudo apt-get install cmake mono-devel mono-gmcs - -# Build libgit2, LibGit2Sharp and run the tests -script: - - git submodule update --init - - mkdir cmake-build - - cd cmake-build - - cmake -DTHREADSAFE=ON -DCMAKE_BUILD_TYPE=Release -DBUILD_CLAR=OFF -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=./libgit2-bin ../libgit2 - - export LD_LIBRARY_PATH=$PWD/libgit2-bin/lib - - cmake --build . --target install - - cd .. - - xbuild CI-build.msbuild /t:Deploy - -# Only watch the development branch -branches: - only: - - vNext - -# Notify development list when needed -notifications: - recipients: - - emeric.fermas@gmail.com - email: - on_success: change - on_failure: always diff --git a/CHANGES.md b/CHANGES.md index e6eed5b7d..a00b598d7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,633 @@ # LibGit2Sharp Changes -**LibGit2Sharp brings all the might and speed of libgit2, a native Git implementation, to the managed world of .Net and Mono.** +## v0.31 - ([diff](https://github.com/libgit2/libgit2sharp/compare/0.30.0..0.31.0)) - - Source code: - - NuGet package: - - Issue tracker: - - CI server: - - @libgit2sharp: +### 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)) + +### Additions + + - Introduce repo.Network.Remotes.Rename() (#730, #741) + - Introduce repo.ObjectDatabase.ShortenObjectId() (#677) + - Introduce Remote.IsSupportedUrl() (#754) + - Introduce repo.CherryPick() (#755, #756) + - Expose advanced conflict data (REUC, renames) (#748) + +### Changes + + - Make Patch expose a richer PatchEntryChanges type (#686, #702) + - Make network operations accept Credentials through a callback (#759, #761, #767) + - Make repo.Index.Stage() respect ignored files by default (#777) + - Make OdbBackend IDisposable (#713) + - Update libgit2 binaries to libgit2/libgit2@d28b2b7 + +### Fixes + + - Don't require specific rights to the parent hierarchy of a repository (#795) + - Prevent Clone() from choking on empty packets (#794) + - Ensure Tags can be created in detached Head state (#791) + - Properly determine object size when calculating its CRC (#783) + - Prevent blind fast forwards merges when there are checkout conflicts (#781) + - Make repo.Reset() and repo.Index.Unstage() cope with renamed entries (#777) + - Do not throw when parsing annotated tags without a Signature (#775, #776) + - Remove conflicts upon repo.Index.Remove() call (#768) + - Honor the merge.ff configuration entry (#709) + - Make Clone() properly throws when passed an invalid url (#701) + +## v0.18.1 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.18.0...v0.18.1)) + +### Additions + + - Make CommitOptions expose additional properties to control how the message should be prettified (#744, #745) + +### Changes + + - Update libgit2 binaries to libgit2/libgit2@90befde + +### Fixes + + - Fix issue when cloning from a different local volume (#742, #743) + +## v0.18.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.17.0...v0.18.0)) + +### Additions + + - Introduce repo.Revert() (#706) + - Enhanced control over Merge behavior through MergeOptions (#685) + - Introduce repo.Network.Remotes.Remove() (#729, #731) + - Teach repo.Network.ListReferences() to accept a Credentials (#647, #704) + - Introduce Reference.IsValidName() (#680, #691) + - Introduce Remote.IsValidName() (#679, #690) + - Expose StatusOptions.RecurseIgnoredDirs (#728) + - Introduce GlobalSettings.Features() (#717) + - Make Repository.Version output the libgit2 built-in features (#676, #694) + +### Changes + + - LibGit2Sharp now requires .Net 4.0 (#654, #678) + - Repository.Checkout() and Branch.Checkout() overloads now accept a CheckoutOptions parameter (#685) + - Deprecate repo.Refs.IsValidName() (#680, #691) + - Deprecate repo.Network.Remotes.IsValidName() (#679, #690) + - Deprecate repo.Branches.Move() in favor of repo.Branches.Rename() (#737, #738) + - Update libgit2 binaries to libgit2/libgit2@2f6f6eb + +### Fixes + + - Do not fail enumerating the ObjectDatabase content when an unexpected file is found under .git/objects (#704) + - Fix update of HEAD when committing against a bare repository with a temporary working directory (#692) + +## v0.17.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.16.0...v0.17.0)) + +### Additions + + - Introduce Network.Pull() (#643 and #65) + - Introduce DefaultCredentials for NTLM/Negotiate authentication (#660) + - Make repo.Merge() accept a Branch (#643) + - Introduce MergeOptions type, to specify the type of merge and whether to commit or not (#643, #662, #663) + - Teach reference altering methods to let the caller control how the reflog is valued (#612, #505 and #389) + - Teach repo.Commits.FindMergeBase to leverage either Standard or Octopus strategy (#634 and #629) + - Make ObjectDatabase.CreateCommit() accept an option controlling the prettifying of the message (#619) + - Allow notes retrieval by namespace and ObjectId (#653) + +### Changes + + - Deprecate repo.Commits.FindCommonAncestor() in favor of repo.Commits.FindMergeBase() (#634) + - Deprecate Network.FetchHeads and Repository.MergeHeads (#643) + - Repository.Commit() overloads now accept a CommitOptions parameter (#668) + - Repository.Clone() now accepts a CloneOptions parameter + - Ease testability by making all GetEnumerator() methods fakeable (#646 and #644) + - Update libgit2 binaries to libgit2/libgit2@bcc6229 + +### Fixes + + - Make Branch.Add() and Branch.Move() use the correct indentity to feed the reflog (#612 and #616) + - Fix NullReferenceException occuring in Repository.Clone (#659 and #635) + +## v0.16.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.15.0...v0.16.0)) + +### Additions + + - Introduce Repository.Merge() (#608 and #620) + - Teach Diff.Compare<>() to return a PatchStats (#610) + +### Changes + + - Speed up NuGet post build copy of the native binaries (#613) + +### Fixes + + - Fix Remotes.Add(name, url, refspec) to prevent the creation of a default fetch refspec beside the passed in one (#614) + - Make LibGit2SharpException.Data expose the correct libgit2 error categories (#601) + +## v0.15.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.14.1...v0.15.0)) + +### Additions + + - Introduce ObjectDatabase.Archive() + - Introduce Repository.Blame() + - Introduce ObjectDatabase.CalculateHistoryDivergence() + - Add Configuration.Find(regexp) + - Add CommitFilter.FirstParentOnly + - Expose Configuration.BuildSignature() + - Add TreeDefinition.Add(string, TreeEntry) + - Make Remote expose its refspecs + +### Changes + + - Make Network.Fetch() accepts optional refspec + - Extend Network.Fetch() and ListReferences() to allow downloading from a url + - Allow Network.Push() to control packbuilder parallelism + - Expose Network.Push() progress reporting + - Extend RemoteUpdater to allow updation of refspecs + - Teach Index.RetrieveStatus to detect renames in index and workdir + - Teach NoteCollection to optionally build a Signature from configuration + - Add RewriteHistoryOptions.OnSucceeding and OnError + - Introduce Blob FilteringOptions + - Rename Blob.ContentAsText() as Blob.GetContentText() + - Rename Blob.ContentStream() as Blob.GetContentStream() + - Deprecate Blob.Content + - Teach Diff.Compare<> to detect renames and copies + - Split Patch and TreeChanges generation + - Deprecate ResetOptions in favor of ResetMode. + - Simplify OdbBackend.ReadPrefix() implementation + - Deprecate ObjectId.StartsWith(byte[], int) in favor of ObjectId.StartsWith(string) + - Update libgit2 binaries to libgit2/libgit2@96fb6a6 + +### Fixes + + - Fix building with Mono on OS X (#557) + - Make RetrieveStatus() reload on-disk index beforehand (#322 and #519) + +## v0.14.1 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.14.0...v0.14.1)) + +### Changes + + - Rename OrphanedHeadException into UnbornBranchException + +### Fixes + + - Fix handling of http->https redirects + - Make probing for libgit2 binaries work from within the NuGet packages folder + - Accept submodule paths with native directory separators + +## v0.14.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.13.0...v0.14.0)) + +### Additions + + - Introduce Blob.ContentAsText() + - Teach repo.Refs.RewriteHistory() to prune empty commits + - Teach repo.Refs.RewriteHistory() to rewrite symbolic references + - Teach repo.ObjectDatabase to enumerate GitObjects + - Teach Branches.Add() and Move() to append to the reflog + - Honor core.logAllRefUpdates configuration setting + - Add strongly-typed LockedFileException + - Add TreeDefinition.Remove(IEnumerable) + - Introduce ObjectId.StartsWith() + - Introduce repo.Config.GetValueOrDefault() + +### Changes + + - Introduce RewriteHistoryOptions type and make repo.Refs.RewriteHistory() leverage it + - Introduce CheckoutOptions type and make repo.CheckoutPaths() leverage it + - Obsolete Blob.ContentAsUnicode and Blob.ContentAsUf8 + - Make OdbBackend interface ObjectId based + - Update libgit2 binaries to libgit2/libgit2@32e4992 + +### Fixes + + - Ensure repo.Network.Push() overloads pass the Credentials down the call chain + - Make SymbolicReference.Target cope with chained symbolic references + - Do not throw when parsing a Remote with no url + - Prevent files or directories starting with ! from being ignored + - Teach Index.Stage to stage files in ignored dirs + +## v0.13.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.12.0...v0.13.0)) + +### Additions + + - Teach Repository to Checkout paths + - Teach Checkout() to cope with revparse extended syntax leading to references + - Make Stash expose Base, Index and Untracked commits + - Teach Repository.Init() to set up a separate git directory + - Teach checkout to report notifications + - Create a new repo.Checkout() overload which accepts a Commit object + - Allow ObjectDatabase.CreateBlob() to limit the number of bytes to consume + - Make ObjectDatabase.CreateBlob() accept a Stream + - Introduce repo.Refs.RewriteHistory() + - Introduce repo.Refs.ReachableFrom() + - Introduce TreeDefinition.From(Commit) + - Expose TagFetchMode property on Remote type + - Add CopyNativeDependencies.targets + +### Changes + + - Rename CheckoutOptions into CheckoutModifiers + - Rename DiffOptions into DiffModifiers + - Rename StashOptions into StashModifiers + - Rename GitSortOptions into CommitSortStrategies + - Rename Filter into CommitFilter + - Rename ObjectDatabase.CreateTag into ObjectDatabase.CreateTagAnnotation + - Obsolete repo.Clone() overload which returns a Repository + - Obsolete repo.Init() overload which returns a Repository + - Obsolete ObjectDatabase.CreateBlob(BinaryReader, string) + - Update libgit2 binaries to libgit2/libgit2@7940036 + +### Fixes + + - Fetch should respect the remote's configured tagopt setting unless explicitly specified + +## v0.12.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.11.0...v0.12.0)) + +### Additions + + - Introduce repo.Info.IsShallow + - Teach repo.Reset to append to the Reflog + - Introduce repo.ObjectDatabase.CreateTag() + - Make repo.Diff.Compare() able to define the expected number of context and interhunk lines (#423) + +### Changes + + - Obsolete TreeEntryTargetType.Tag + - Update libgit2 binaries to libgit2/libgit2@9d9fff3 + +### Fixes + + - Change probing mechanism to rely on specifically named versions of libgit2 binaries (#241) + - Ensure that two versions of LibGit2Sharp can run side by side (#241) + +## v0.11.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.10.0...v0.11.0)) + +### Additions + + - Introduce Repository.Refs.Log() + - Teach Checkout() and Commit() to append to the reflog + - Teach Refs.Add(), Refs.UpdateTarget() to optionally append to the reflog + - Add Repository.Submodules namespace + - Add submodule support to Index.Stage() + - Add TreeDefinition.Add(Submodule) and TreeDefinition.AddGitLink() + - Introduce ExplicitPathsOptions type to control handling of unmatched pathspecs + - Make Index.Remove(), Index.Unstage()/Stage(), Diff.Compare() and Reset() accept ExplicitPathsOptions + - Add an indexer to the StashCollection + - Add the UpstreamBranchCanonicalName property to Branch + - Make Push accept Branch instances + - Introduce Reference.IsTag, Reference.IsLocalBranch and Reference.IsRemoteTrackingBranch + - Add Repository.IsValid() + - Refine build resilience on Linux + +### Changes + + - Obsolete Tree.Trees and Tree.Blobs properties + - Replace GitObjectType with ObjectType and TreeEntryTargetType + - Rename TreeEntry.Type and TreeEntryDefinition.Type to *.TargetType + - Move Repository.Conflicts to Index.Conflicts + - Move Remote.Fetch() in Repository.Network + - Modify StashCollection.Remove() to accept an integer param rather than a revparse expression + - Rename BranchUpdater.Upstream to TrackedBranch + - Rename BranchUpdater.UpstreamMergeBranch to UpstreamBranch + - Rename BranchUpdater.UpstreamRemote to Remote + +### Fixes + + - Make Commit() append to the reflog (#371) + - Make Index.Remove() able to only remove from index (#270) + - Teach Index.Remove() to clear the associated conflicts (#325) + - Make Index.Remove() able to remove folders (#327) + - Fix repo.Checkout() when working against repo.Head + - Fix update of the target of repo.Refs.Head + - Teach Checkout() to cope with revparse syntax + - Support TreeEntry.Target for GitLink entries + +## v0.10.0 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.9.5...v0.10.0)) + +### Additions + + - Update working directory on checkout + - New network related features: clone, fetch, push, list remote references + - Expose the heads that have been updated during the last fetch in Repository.Network.FetchHeads + - Introduce Repository.Network.Remotes.IsValidName() + - New .gitignore related features: temporary rules, path checking + - Add support for custom, managed ODB backends + - Add revparse support in Repository.Lookup() + - Improve Repository.Commit(): add merged branches as parents, cleanup merge data + - Introduce Blob.IsBinary + - Add strongly-typed exceptions (NonFastForwardException, UnmergedIndexEntriesException, ...) + - Add basic stashing support: add, retrieve, list and remove + - Add git clean support in Repository.RemoveUntrackedFiles() + - Add shortcut to HEAD in Repository.Refs.Head + - Introduce Repository.Refs.IsValidName() + - Add Repository.Refs.FromGlob() to enumerate references matching a specified glob + - Add support for XDG configuration store + - Make Config.Get() and Config.Delete() able to target a specific store + - Diff.Compare() enhancements: work against workdir and index, consider untracked changes, expose typechanges + - Allow retrieval of the remote of a non-local branch through Branch.Remote + - Allow modification of the branch properties through Repository.Branches.Update() + - Expose merge related information: Repository.Index.IsFullyMerged, Repository.Conflicts, IndexEntry.StageLevel + - Expose the heads being merged in Repository.MergeHeads + - Introduce IndexEntry.Mode + - Add more repository information: Repository.Info.CurrentOperation, Repository.Info.Message, Repository.Info.IsHeadOrphaned + - Allow passing an optional RepositoryOptions to Repository.Init() + - Allow reset filtering by passing a list of paths to consider + +### Changes + + - Make TreeChanges and TreeEntryChanges expose native paths + - Make Repository.Reset accept a Commit instead of a string + - Stop sorting collections (references, remotes, notes ...) + - Move AheadBy/BehindBy into new Branch.TrackingDetails + - Move Repository.Remotes to Repository.Network.Remotes + - Move Configuration.HasXXXConfig() to Configuration.HasConfig() + - Rename CommitCollection to CommitLog + - Rename LibGit2Exception to LibGit2SharpException + - Rename Delete() to Unset() in Configuration + - Rename Delete() to Remove() in TagCollection, ReferenceCollection, NoteCollection, BranchCollection + - Rename Create() to Add() in TagCollection, BranchCollection, ReferenceCollection, RemoteCollection, NoteCollection + - Obsolete RepositoryInformation.IsEmpty, DiffTarget, IndexEntry.State, Commit.ParentsCount + +### Fixes + + - Allow abstracting LibGit2Sharp in testing context (#138) + - Ease the detection of a specific key in a specific store (#162) + - Expose libgit2 error information through the LibGit2SharpException.Data property(#137) + - Preserve non-ASCII characters in commit messages (#191) + - Fix retrieval of the author of a commit (#242) + - Prevent duplicated tree entries in commits (#243) + - Fix Repository.Discover behaviour with UNC paths (#256) + - Make Index.Unstage work against an orphaned head (#257) + - Make IsTracking & TrackedBranch property not throw for a detached head (#266, #268) ## v0.9.5 - ([diff](https://github.com/libgit2/libgit2sharp/compare/v0.9.0...v0.9.5)) @@ -36,7 +657,7 @@ - Direct creation or Blobs, Trees and Commits without the workdir nor index involvement (#135) - New Diff namespace: supports tree-to-tree, tree-to-index and blob-to-blob comparisons (#136) - Add Commits.FindCommonAncestor() (#149) - + ### Changes - Deprecate repo.Branches.Checkout() in favor of repo.Checkout() diff --git a/CI-build.msbuild b/CI-build.msbuild deleted file mode 100644 index 4dc2ca435..000000000 --- a/CI-build.msbuild +++ /dev/null @@ -1,55 +0,0 @@ - - - Release - $(MSBuildProjectDirectory) - $(RootDir)\LibGit2Sharp.Tests\bin\$(Configuration) - $(RootDir)\Build - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 f2da33d8b..c70554345 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2011-2013 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/MoQ/Moq.dll b/Lib/MoQ/Moq.dll deleted file mode 100644 index 3d3b8ccd0..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 432077576..000000000 --- a/Lib/MoQ/Moq.xml +++ /dev/null @@ -1,5768 +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. - - - - - 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]); - - - - - - 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); - - - - - - Language for ReturnSequence - - - - - Returns value - - - - - Throws an exception - - - - - Throws an exception - - - - - 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. - - - - - 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. - - - - - Implements the fluent API. - - - - - Encapsulates a method that has five parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has five parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has six parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has six parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has seven parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has seven parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has eight parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has eight parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has nine parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has nine parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has ten parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has ten parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has eleven parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has eleven parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has twelve parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has twelve parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has thirteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has thirteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has fourteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has fourteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has fifteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has fifteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - Encapsulates a method that has sixteen parameters and does not return a value. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the sixteenth parameter of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The sixteenth parameter of the method that this delegate encapsulates. - - - - Encapsulates a method that has sixteen parameters and returns a value of the type specified by the parameter. - - The type of the first parameter of the method that this delegate encapsulates. - The type of the second parameter of the method that this delegate encapsulates. - The type of the third parameter of the method that this delegate encapsulates. - The type of the fourth parameter of the method that this delegate encapsulates. - The type of the fifth parameter of the method that this delegate encapsulates. - The type of the sixth parameter of the method that this delegate encapsulates. - The type of the seventh parameter of the method that this delegate encapsulates. - The type of the eighth parameter of the method that this delegate encapsulates. - The type of the nineth parameter of the method that this delegate encapsulates. - The type of the tenth parameter of the method that this delegate encapsulates. - The type of the eleventh parameter of the method that this delegate encapsulates. - The type of the twelfth parameter of the method that this delegate encapsulates. - The type of the thirteenth parameter of the method that this delegate encapsulates. - The type of the fourteenth parameter of the method that this delegate encapsulates. - The type of the fifteenth parameter of the method that this delegate encapsulates. - The type of the sixteenth parameter of the method that this delegate encapsulates. - The type of the return value of the method that this delegate encapsulates. - The first parameter of the method that this delegate encapsulates. - The second parameter of the method that this delegate encapsulates. - The third parameter of the method that this delegate encapsulates. - The fourth parameter of the method that this delegate encapsulates. - The fifth parameter of the method that this delegate encapsulates. - The sixth parameter of the method that this delegate encapsulates. - The seventh parameter of the method that this delegate encapsulates. - The eighth parameter of the method that this delegate encapsulates. - The nineth parameter of the method that this delegate encapsulates. - The tenth parameter of the method that this delegate encapsulates. - The eleventh parameter of the method that this delegate encapsulates. - The twelfth parameter of the method that this delegate encapsulates. - The thirteenth parameter of the method that this delegate encapsulates. - The fourteenth parameter of the method that this delegate encapsulates. - The fifteenth parameter of the method that this delegate encapsulates. - The sixteenth parameter of the method that this delegate encapsulates. - The return value of the method that this delegate encapsulates. - - - - 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 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, - 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. 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, 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, 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 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, 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. - - - - 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. - - - - - 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. - - - - - 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. - - - - - 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 - - - - Given a type return all of its ancestors, both types and interfaces. - - The type to find immediate ancestors of - - - - 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 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 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. - - - - 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 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. - - - - - 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.dll b/Lib/NativeBinaries/amd64/git2.dll deleted file mode 100644 index 542676413..000000000 Binary files a/Lib/NativeBinaries/amd64/git2.dll and /dev/null differ diff --git a/Lib/NativeBinaries/amd64/git2.pdb b/Lib/NativeBinaries/amd64/git2.pdb deleted file mode 100644 index 7843c6029..000000000 Binary files a/Lib/NativeBinaries/amd64/git2.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.dll b/Lib/NativeBinaries/x86/git2.dll deleted file mode 100644 index bf2aab770..000000000 Binary files a/Lib/NativeBinaries/x86/git2.dll and /dev/null differ diff --git a/Lib/NativeBinaries/x86/git2.pdb b/Lib/NativeBinaries/x86/git2.pdb deleted file mode 100644 index 38d9e1e0e..000000000 Binary files a/Lib/NativeBinaries/x86/git2.pdb and /dev/null differ diff --git a/Lib/NuGet/NuGet.exe b/Lib/NuGet/NuGet.exe deleted file mode 100644 index c3960af62..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 new file mode 100644 index 000000000..19860ca0b --- /dev/null +++ b/LibGit2Sharp.Tests/ArchiveFixture.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class ArchiveFixture : BaseFixture + { + [Fact] + public void CanArchiveATree() + { + 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 + { + new { Path = "1", Sha = "7f76480d939dc401415927ea7ef25c676b8ddb8f" }, + new { Path = Path.Combine("1", "branch_file.txt"), Sha = "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, + new { Path = "README", Sha = "a8233120f6ad708f843d861ce2b7228ec4e3dec6" }, + new { Path = "branch_file.txt", Sha = "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, + new { Path = "new.txt", Sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" }, + }; + Assert.Equal(expected, archiver.Files); + Assert.Null(archiver.ReceivedCommitSha); + Assert.InRange(archiver.ModificationTime, before, DateTimeOffset.UtcNow); + } + } + + [Fact] + public void CanArchiveACommit() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var commit = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + + var archiver = new MockArchiver(); + + repo.ObjectDatabase.Archive(commit, archiver); + + var expected = new ArrayList + { + new { Path = "1", Sha = "7f76480d939dc401415927ea7ef25c676b8ddb8f" }, + new { Path = Path.Combine("1", "branch_file.txt"), Sha = "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, + new { Path = "README", Sha = "a8233120f6ad708f843d861ce2b7228ec4e3dec6" }, + new { Path = "branch_file.txt", Sha = "45b983be36b73c0788dc9cbcb76cbb80fc7bb057" }, + new { Path = "new.txt", Sha = "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd" }, + }; + Assert.Equal(expected, archiver.Files); + Assert.Equal(commit.Sha, archiver.ReceivedCommitSha); + Assert.Equal(commit.Committer.When, archiver.ModificationTime); + } + } + + [Fact] + public void ArchivingANullTreeOrCommitThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + 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))); + } + } + + #region MockArchiver + + private class MockArchiver : ArchiverBase + { + public readonly ArrayList Files = new ArrayList(); + public string ReceivedCommitSha; + public DateTimeOffset ModificationTime; + + #region Overrides of ArchiverBase + + public override void BeforeArchiving(Tree tree, ObjectId oid, DateTimeOffset modificationTime) + { + if (oid != null) + { + ReceivedCommitSha = oid.Sha; + } + ModificationTime = modificationTime; + } + + protected override void AddTreeEntry(string path, TreeEntry entry, DateTimeOffset modificationTime) + { + Files.Add(new { Path = path, entry.Target.Sha }); + } + + #endregion + } + + #endregion + } +} diff --git a/LibGit2Sharp.Tests/ArchiveTarFixture.cs b/LibGit2Sharp.Tests/ArchiveTarFixture.cs new file mode 100644 index 000000000..247a9a3b0 --- /dev/null +++ b/LibGit2Sharp.Tests/ArchiveTarFixture.cs @@ -0,0 +1,44 @@ +using System.IO; +using System.Text; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class ArchiveTarFixture : BaseFixture + { + [Fact] + public void CanArchiveACommitWithDirectoryAsTar() + { + var path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + // This tests generates an archive of the bare test repo, and compares it with + // a pre-generated tar file. The expected tar file has been generated with the + // crlf filter active (on windows), so we need to make sure that even if the test + // is launched on linux then the files content has the crlf filter applied (not + // active by default). + var sb = new StringBuilder(); + sb.Append("* text eol=crlf\n"); + Touch(Path.Combine(repo.Info.Path, "info"), "attributes", sb.ToString()); + + var commit = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + + var scd = BuildSelfCleaningDirectory(); + var archivePath = Path.Combine(scd.RootedDirectoryPath, Path.GetRandomFileName() + ".tar"); + Directory.CreateDirectory(scd.RootedDirectoryPath); + + repo.ObjectDatabase.Archive(commit, 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(); + + Assert.Equal(expected, actual); + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/AttributesFixture.cs b/LibGit2Sharp.Tests/AttributesFixture.cs index 1f7c6efe5..3ac8326d3 100644 --- a/LibGit2Sharp.Tests/AttributesFixture.cs +++ b/LibGit2Sharp.Tests/AttributesFixture.cs @@ -1,5 +1,4 @@ -using System.IO; -using System.Text; +using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -10,8 +9,7 @@ public class AttributesFixture : BaseFixture [Fact] public void StagingHonorsTheAttributesFiles() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFile(repo); @@ -21,7 +19,7 @@ public void StagingHonorsTheAttributesFiles() } } - private static void AssertNormalization(Repository repo, string filename, bool shouldHaveBeenNormalized, string expectedSha) + private static void AssertNormalization(IRepository repo, string filename, bool shouldHaveBeenNormalized, string expectedSha) { var sb = new StringBuilder(); sb.Append("I'm going to be dynamically processed\r\n"); @@ -29,9 +27,9 @@ private static void AssertNormalization(Repository repo, string filename, bool s sb.Append("...are going to be\n"); sb.Append("normalized!\r\n"); - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), sb.ToString()); + Touch(repo.Info.WorkingDirectory, filename, sb.ToString()); - repo.Index.Stage(filename); + Commands.Stage(repo, filename); IndexEntry entry = repo.Index[filename]; Assert.NotNull(entry); @@ -41,20 +39,17 @@ private static void AssertNormalization(Repository repo, string filename, bool s var blob = repo.Lookup(entry.Id); Assert.NotNull(blob); - Assert.Equal(!shouldHaveBeenNormalized, blob.ContentAsUtf8().Contains("\r")); + Assert.Equal(!shouldHaveBeenNormalized, blob.GetContentText().Contains("\r")); } - private static void CreateAttributesFile(Repository repo) + private static void CreateAttributesFile(IRepository repo) { - const string relativePath = ".gitattributes"; - string fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); - var sb = new StringBuilder(); sb.Append("* text=auto\n"); sb.Append("*.txt text\n"); sb.Append("*.data binary\n"); - File.WriteAllText(fullFilePath, sb.ToString()); + Touch(repo.Info.WorkingDirectory, ".gitattributes", sb.ToString()); } } } diff --git a/LibGit2Sharp.Tests/BlameFixture.cs b/LibGit2Sharp.Tests/BlameFixture.cs new file mode 100644 index 000000000..8cefcfb45 --- /dev/null +++ b/LibGit2Sharp.Tests/BlameFixture.cs @@ -0,0 +1,86 @@ +using System; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class BlameFixture : BaseFixture + { + private static void AssertCorrectHeadBlame(BlameHunkCollection blame) + { + 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)); + + Assert.Equal(0, blame.HunkForLine(0).FinalStartLineNumber); + Assert.Equal("schacon@gmail.com", blame.HunkForLine(0).FinalSignature.Email); + Assert.Equal("4a202b3", blame.HunkForLine(0).FinalCommit.Id.ToString(7)); + } + + [Fact] + public void CanBlameSimply() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + AssertCorrectHeadBlame(repo.Blame("README")); + } + } + + [Fact] + public void CanBlameFromADifferentCommit() + { + string path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) + { + // File doesn't exist at HEAD + Assert.Throws(() => repo.Blame("ancestor-only.txt")); + + var blame = repo.Blame("ancestor-only.txt", new BlameOptions { StartingAt = "9107b30" }); + Assert.Single(blame); + } + } + + [Fact] + public void ValidatesLimits() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var blame = repo.Blame("README"); + + Assert.Throws(() => blame[1]); + Assert.Throws(() => blame.HunkForLine(2)); + } + } + + [Fact] + public void CanBlameFromVariousTypes() + { + 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"] })); + } + } + + [Fact] + public void CanStopBlame() + { + 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.StartsWith("be3563a", blame[0].FinalCommit.Sha); + } + } + } +} diff --git a/LibGit2Sharp.Tests/BlobFixture.cs b/LibGit2Sharp.Tests/BlobFixture.cs index 42fecf6c5..314dea379 100644 --- a/LibGit2Sharp.Tests/BlobFixture.cs +++ b/LibGit2Sharp.Tests/BlobFixture.cs @@ -1,4 +1,5 @@ using System.IO; +using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -8,23 +9,93 @@ namespace LibGit2Sharp.Tests public class BlobFixture : BaseFixture { [Fact] - public void CanGetBlobAsUtf8() + 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(); - string text = blob.ContentAsUtf8(); Assert.Equal("hey there\n", text); } } + [SkippableTheory] + [InlineData("false", "hey there\n")] + [InlineData("input", "hey there\n")] + [InlineData("true", "hey there\r\n")] + public void CanGetBlobAsFilteredText(string autocrlf, string expectedText) + { + SkipIfNotSupported(autocrlf); + + 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")); + + Assert.Equal(expectedText, text); + } + } + +#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")] + [InlineData("utf-8", 7, "EF BB BF 31 32 33 34")] + [InlineData("utf-16", 10, "FF FE 31 00 32 00 33 00 34 00")] + [InlineData("unicodeFFFE", 10, "FE FF 00 31 00 32 00 33 00 34")] + [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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var bomFile = "bom.txt"; + var content = "1234"; + var encoding = Encoding.GetEncoding(encodingName); + + var bomPath = Touch(repo.Info.WorkingDirectory, bomFile, content, encoding); + Assert.Equal(expectedContentBytes, File.ReadAllBytes(bomPath).Length); + + 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()) + { + Assert.Equal(expectedContentBytes, stream.Length); + } + + var textDetected = blob.GetContentText(); + Assert.Equal(content, textDetected); + + var text = blob.GetContentText(encoding); + Assert.Equal(content, text); + + var utf7Chars = blob.GetContentText(Encoding.UTF7).Select(c => ((int)c).ToString("X2")).ToArray(); + 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); } } @@ -32,85 +103,116 @@ 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 CanReadBlobContent() + public void CanReadBlobStream() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - byte[] bytes = blob.Content; - Assert.Equal(10, bytes.Length); + Assert.False(blob.IsMissing); + + var contentStream = blob.GetContentStream(); + Assert.Equal(blob.Size, contentStream.Length); - string content = Encoding.UTF8.GetString(bytes); - Assert.Equal("hey there\n", content); + using (var tr = new StreamReader(contentStream, Encoding.UTF8)) + { + string content = tr.ReadToEnd(); + Assert.Equal("hey there\n", content); + } } } - [Fact] - public void CanReadBlobStream() + [SkippableTheory] + [InlineData("false", "hey there\n")] + [InlineData("input", "hey there\n")] + [InlineData("true", "hey there\r\n")] + public void CanReadBlobFilteredStream(string autocrlf, string expectedContent) { - using (var repo = new Repository(BareTestRepoPath)) + SkipIfNotSupported(autocrlf); + + 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); - using (var tr = new StreamReader(blob.ContentStream, Encoding.UTF8)) + using (var tr = new StreamReader(contentStream, Encoding.UTF8)) { string content = tr.ReadToEnd(); - Assert.Equal("hey there\n", content); + + Assert.Equal(expectedContent, content); } } } - public static void CopyStream(Stream input, Stream output) + [Fact] + public void CanReadBlobFilteredStreamOfUnmodifiedBinary() { - // Reused from the following Stack Overflow post with permission - // of Jon Skeet (obtained on 25 Feb 2013) - // http://stackoverflow.com/questions/411592/how-do-i-save-a-stream-to-a-file/411605#411605 - var buffer = new byte[8*1024]; - int len; - while ((len = input.Read(buffer, 0, buffer.Length)) > 0) + var binaryContent = new byte[] { 0, 1, 2, 3, 4, 5 }; + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - output.Write(buffer, 0, len); + 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"))) + { + Assert.Equal(blob.Size, filtered.Length); + Assert.True(StreamEquals(stream, filtered)); + } + } } } [Fact] public void CanStageAFileGeneratedFromABlobContentStream() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (Repository repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { for (int i = 0; i < 5; i++) { 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.ContentStream) + using (Stream stream = blob.GetContentStream()) using (Stream file = File.OpenWrite(Path.Combine(repo.Info.WorkingDirectory, "small.fromblob.txt"))) { 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); @@ -120,11 +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" && Constants.IsRunningOnUnix, "Non-Windows does not support core.autocrlf = true"); + } } } diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index faa3290db..88247e256 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -1,9 +1,9 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -16,111 +16,255 @@ public class BranchFixture : BaseFixture [InlineData("Ångström")] public void CanCreateBranch(string name) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { - Branch newBranch = repo.CreateBranch(name, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); + 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("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", newBranch.Tip.Sha); - Assert.NotNull(repo.Branches.SingleOrDefault(p => p.Name == name)); + Assert.Equal(committish, newBranch.Tip.Sha); + + // Note the call to String.Normalize(). This is because, on Mac OS X, the filesystem + // decomposes the UTF-8 characters on write, which results in a different set of bytes + // 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.FriendlyName.Normalize() == name)); + + AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + committish, + null, + newBranch.Tip.Id, + Constants.Identity, before); + + repo.Branches.Remove(newBranch.FriendlyName); + Assert.Null(repo.Branches[name]); + } + } - repo.Branches.Remove(newBranch.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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + // No branch named orphan + Assert.Null(repo.Branches["orphan"]); + + // HEAD doesn't point to an unborn branch + Assert.False(repo.Info.IsHeadUnborn); + + // Let's move the HEAD to this branch to be created + repo.Refs.UpdateTarget("HEAD", "refs/heads/orphan"); + Assert.True(repo.Info.IsHeadUnborn); + + // The branch still doesn't exist + Assert.Null(repo.Branches["orphan"]); + + // Create a commit against HEAD + Commit c = repo.Commit("New initial root commit", Constants.Signature, Constants.Signature); + + // Ensure this commit has no parent + Assert.Empty(c.Parents); + + // The branch now exists... + Branch orphan = repo.Branches["orphan"]; + Assert.NotNull(orphan); + AssertBelongsToARepository(repo, orphan); + + // ...and points to that newly created commit + Assert.Equal(c, orphan.Tip); } } [Fact] public void CanCreateBranchUsingAbbreviatedSha() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + const string name = "unit_test"; - Branch newBranch = repo.CreateBranch(name, "be3563a"); + 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, + Constants.Identity, before); } } - [Fact] - public void CanCreateBranchFromImplicitHead() + [Theory] + [InlineData("32eab9cb1f450b5fe7ab663462b77d7f4b703344")] + [InlineData("master")] + public void CanCreateBranchFromImplicitHead(string headCommitOrBranchSpec) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + + 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("4c062a6361ae6959e06292c1fa5e2822d9c96345", newBranch.Tip.Sha); - Assert.NotNull(repo.Branches.SingleOrDefault(p => p.Name == name)); + Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newBranch.Tip.Sha); + Assert.NotNull(repo.Branches.SingleOrDefault(p => p.FriendlyName == name)); + + AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from " + headCommitOrBranchSpec, + null, + newBranch.Tip.Id, + Constants.Identity, before); } } - [Fact] - public void CanCreateBranchFromExplicitHead() + [Theory] + [InlineData("32eab9cb1f450b5fe7ab663462b77d7f4b703344")] + [InlineData("master")] + public void CanCreateBranchFromExplicitHead(string headCommitOrBranchSpec) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + + 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("4c062a6361ae6959e06292c1fa5e2822d9c96345", newBranch.Tip.Sha); + Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newBranch.Tip.Sha); + + AssertRefLogEntry(repo, newBranch.CanonicalName, + "branch: Created from HEAD", + null, + newBranch.Tip.Id, + Constants.Identity, before); } } [Fact] public void CanCreateBranchFromCommit() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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, + Constants.Identity, before); } } [Fact] public void CanCreateBranchFromRevparseSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + const string name = "revparse_branch"; - var target = repo.Lookup("master~2"); - Branch newBranch = repo.CreateBranch(name, target); + 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, + Constants.Identity, before); } } - [Fact] - public void CreatingABranchFromATagPeelsToTheCommit() + [Theory] + [InlineData("test")] + [InlineData("refs/tags/test")] + public void CreatingABranchFromATagPeelsToTheCommit(string committish) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + const string name = "i-peel-tag"; - Branch newBranch = repo.CreateBranch(name, "refs/tags/test"); + + 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, + Constants.Identity, before); } } [Fact] public void CreatingABranchTriggersTheCreationOfADirectReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch newBranch = repo.CreateBranch("clone-of-master"); Assert.False(newBranch.IsCurrentRepositoryHead); @@ -130,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)); @@ -181,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, repo.Branches.Select(b => b.Name).ToArray()); + Assert.Equal(expectedBranches, SortedBranches(repo.Branches, b => b.FriendlyName)); Assert.Equal(5, repo.Branches.Count()); } @@ -192,44 +341,44 @@ public void CanListAllBranches() [Fact] public void CanListBranchesWithRemoteAndLocalBranchWithSameShortName() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Create a local branch with the same short name as a remote branch. repo.Branches.Add("origin/master", repo.Branches["origin/test"].Tip); var expectedWdBranches = new[] { - "diff-test-cases", "i-do-numbers", "logo", "master", "origin/master", "track-local", + "diff-test-cases", "i-do-numbers", "logo", "master", "origin/master", "track-local", "treesame_as_32eab" }; - Assert.Equal(expectedWdBranches, repo.Branches - .Where(b => !b.IsRemote) - .Select(b => b.Name).ToArray()); + Assert.Equal(expectedWdBranches, + 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[] { - "diff-test-cases", "i-do-numbers", "logo", "master", "track-local", + "diff-test-cases", "i-do-numbers", "logo", "master", "track-local", "treesame_as_32eab", "origin/HEAD", "origin/br2", "origin/master", "origin/packed-test", "origin/test" }; - Assert.Equal(expectedWdBranches, repo.Branches.Select(b => b.Name).ToArray()); + 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[] { @@ -238,33 +387,38 @@ public void CanListAllBranchesIncludingRemoteRefs() new { Name = "logo", Sha = "a447ba2ca8fffd46dece72f7db6faf324afb8fcd", IsRemote = false }, new { Name = "master", Sha = "32eab9cb1f450b5fe7ab663462b77d7f4b703344", IsRemote = false }, new { Name = "track-local", Sha = "580c2111be43802dab11328176d94c391f1deae9", IsRemote = false }, + new { Name = "treesame_as_32eab", Sha = "f705abffe7015f2beacf2abe7a36583ebee3487e", IsRemote = false }, new { Name = "origin/HEAD", Sha = "580c2111be43802dab11328176d94c391f1deae9", IsRemote = true }, new { Name = "origin/br2", Sha = "a4a7dce85cf63874e984719f4fdd239f5145052f", IsRemote = true }, new { Name = "origin/master", Sha = "580c2111be43802dab11328176d94c391f1deae9", IsRemote = true }, new { Name = "origin/packed-test", Sha = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", IsRemote = true }, new { Name = "origin/test", Sha = "e90810b8df3e80c413d903f631643c716887138d", IsRemote = true }, }; - Assert.Equal(expectedBranchesIncludingRemoteRefs, repo.Branches.Select(b => new { b.Name, b.Tip.Sha, b.IsRemote }).ToArray()); + Assert.Equal(expectedBranchesIncludingRemoteRefs, + 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 RemoteForNonTrackingBranchIsNull() + 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); } } @@ -272,25 +426,95 @@ public void RemoteForNonTrackingBranchIsNull() 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() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Branch trackLocal = repo.Branches["track-local"]; + Assert.Equal("refs/heads/master", trackLocal.UpstreamBranchCanonicalName); + } + } + + [Fact] + public void QueryRemoteForRemoteBranch() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var master = repo.Branches["origin/master"]; + Assert.Equal("origin", master.RemoteName); + } + } + + [Fact] + public void QueryUnresolvableRemoteForRemoteBranch() + { + var fetchRefSpecs = new string[] { "+refs/heads/notfound/*:refs/remotes/origin/notfound/*" }; + + 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("origin", r => r.FetchRefSpecs = fetchRefSpecs); + + Branch branch = repo.Branches["refs/remotes/origin/master"]; + + Assert.NotNull(branch); + Assert.True(branch.IsRemote); + + Assert.Null(branch.RemoteName); + } + } + + [Fact] + public void QueryAmbigousRemoteForRemoteBranch() + { + const string fetchRefSpec = "+refs/heads/*:refs/remotes/origin/*"; + const string url = "http://github.com/libgit2/TestGitRepository"; + + 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. + repo.Network.Remotes.Add("ambiguous", url, fetchRefSpec); + + Branch branch = repo.Branches["refs/remotes/origin/master"]; + + Assert.NotNull(branch); + Assert.True(branch.IsRemote); + + 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)); @@ -300,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); @@ -315,8 +540,8 @@ public void CanLookupLocalBranch() [Fact] public void CanLookupABranchWhichNameIsMadeOfNon7BitsAsciiCharacters() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string name = "Ångström"; Branch newBranch = repo.CreateBranch(name, "be3563a"); @@ -331,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]); @@ -339,99 +565,150 @@ public void LookingOutABranchByNameWithBadParamsThrows() } } + [Fact] public void CanGetInformationFromUnbornBranch() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath, true)) + string repoPath = InitNewRepository(true); + + using (var repo = new Repository(repoPath)) { 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?"]); - Assert.Null(head.AheadBy); - Assert.Null(head.BehindBy); Assert.False(head.IsTracking); Assert.Null(head.TrackedBranch); + + Assert.NotNull(head.TrackingDetails); + Assert.Null(head.TrackingDetails.AheadBy); + Assert.Null(head.TrackingDetails.BehindBy); + Assert.Null(head.TrackingDetails.CommonAncestor); } } [Fact] public void CanGetTrackingInformationFromBranchSharingNoHistoryWithItsTrackedBranch() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; - repo.Refs.UpdateTarget("refs/remotes/origin/master", "origin/test"); + const string logMessage = "update target message"; + repo.Refs.UpdateTarget("refs/remotes/origin/master", "origin/test", logMessage); + + Assert.True(master.IsTracking); + Assert.NotNull(master.TrackedBranch); + AssertBelongsToARepository(repo, master.TrackedBranch); + + Assert.NotNull(master.TrackingDetails); + Assert.Equal(9, master.TrackingDetails.AheadBy); + Assert.Equal(2, master.TrackingDetails.BehindBy); + Assert.Null(repo.Head.TrackingDetails.CommonAncestor); + // Assert reflog entry is created + var reflogEntry = repo.Refs.Log("refs/remotes/origin/master").First(); + Assert.Equal(repo.Branches["origin/test"].Tip.Id, reflogEntry.To); + Assert.Equal(logMessage, reflogEntry.Message); + } + } + + [Fact] + public void TrackingInformationIsEmptyForBranchTrackingPrunedRemoteBranch() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + const string remoteRef = "refs/remotes/origin/master"; + repo.Refs.Remove(remoteRef); + + Branch master = repo.Branches["master"]; Assert.True(master.IsTracking); - Assert.Null(master.AheadBy); - Assert.Null(master.BehindBy); Assert.NotNull(master.TrackedBranch); + Assert.Equal(remoteRef, master.TrackedBranch.CanonicalName); + Assert.Null(master.TrackedBranch.Tip); + + Assert.NotNull(master.TrackingDetails); + Assert.Null(master.TrackingDetails.AheadBy); + Assert.Null(master.TrackingDetails.BehindBy); + Assert.Null(master.TrackingDetails.CommonAncestor); } } [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); Assert.Null(branch.TrackedBranch); - Assert.Null(branch.AheadBy); - Assert.Null(branch.BehindBy); + + Assert.NotNull(branch.TrackingDetails); + Assert.Null(branch.TrackingDetails.AheadBy); + Assert.Null(branch.TrackingDetails.BehindBy); + Assert.Null(branch.TrackingDetails.CommonAncestor); } } [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); Assert.Equal(repo.Branches["refs/remotes/origin/master"], master.TrackedBranch); - Assert.Equal(2, master.AheadBy); - Assert.Equal(2, master.BehindBy); + + Assert.NotNull(master.TrackingDetails); + Assert.Equal(2, master.TrackingDetails.AheadBy); + Assert.Equal(2, master.TrackingDetails.BehindBy); + Assert.Equal(repo.Lookup("4c062a6"), master.TrackingDetails.CommonAncestor); } } [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); Assert.Equal(repo.Branches["master"], branch.TrackedBranch); - Assert.Equal(2, branch.AheadBy); - Assert.Equal(2, branch.BehindBy); + + Assert.NotNull(branch.TrackingDetails); + Assert.Equal(2, branch.TrackingDetails.AheadBy); + Assert.Equal(2, branch.TrackingDetails.BehindBy); + Assert.Equal(repo.Lookup("4c062a6"), branch.TrackingDetails.CommonAncestor); } } [Fact] - public void MovingARemoteTrackingBranchThrows() + 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); - Assert.Throws(() => repo.Branches.Move(master, "new_name", true)); + Assert.Throws(() => repo.Branches.Rename(master, "new_name", true)); } } [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()); @@ -439,21 +716,22 @@ public void CanWalkCommitsFromAnotherBranch() } [Fact] - public void CanSetUpstreamBranch() + public void CanSetTrackedBranch() { const string testBranchName = "branchToSetUpstreamInfoFor"; - const string upstreamBranchName = "refs/remotes/origin/master"; + const string trackedBranchName = "refs/remotes/origin/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + 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 upstreamBranch = repo.Branches[upstreamBranchName]; repo.Branches.Update(branch, - b => b.Upstream = upstreamBranch.CanonicalName); + b => b.TrackedBranch = trackedBranch.CanonicalName); // Verify the immutability of the branch. Assert.False(branch.IsTracking); @@ -465,35 +743,96 @@ public void CanSetUpstreamBranch() Assert.NotNull(upstreamRemote); Assert.True(branch.IsTracking); - Assert.Equal(upstreamBranch, branch.TrackedBranch); - Assert.Equal(upstreamRemote, branch.Remote); + Assert.Equal(trackedBranch, branch.TrackedBranch); + Assert.Equal("origin", branch.RemoteName); + } + } + + [Fact] + public void SetTrackedBranchForUnreasolvableRemoteThrows() + { + const string testBranchName = "branchToSetUpstreamInfoFor"; + const string trackedBranchName = "refs/remotes/origin/master"; + var fetchRefSpecs = new string[] { "+refs/heads/notfound/*:refs/remotes/origin/notfound/*" }; + + 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("origin", r => r.FetchRefSpecs = fetchRefSpecs); + + // Now attempt to update the tracked branch + Branch branch = repo.CreateBranch(testBranchName); + Assert.False(branch.IsTracking); + + Branch trackedBranch = repo.Branches[trackedBranchName]; + + Assert.Throws(() => repo.Branches.Update(branch, + b => b.TrackedBranch = trackedBranch.CanonicalName)); } } [Fact] - public void CanSetLocalUpstreamBranch() + public void CanSetUpstreamBranch() { const string testBranchName = "branchToSetUpstreamInfoFor"; const string upstreamBranchName = "refs/heads/master"; + const string trackedBranchName = "refs/remotes/origin/master"; + const string remoteName = "origin"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Branch trackedBranch = repo.Branches[trackedBranchName]; + Assert.True(trackedBranch.IsRemote); - using (var repo = new Repository(path.RepositoryPath)) + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); + Assert.False(branch.IsTracking); + + Branch updatedBranch = repo.Branches.Update(branch, + b => b.Remote = remoteName, + b => b.UpstreamBranch = upstreamBranchName); + + // Verify the immutability of the branch. + Assert.False(branch.IsTracking); + + Remote upstreamRemote = repo.Network.Remotes[remoteName]; + Assert.NotNull(upstreamRemote); + + Assert.True(updatedBranch.IsTracking); + Assert.Equal(trackedBranch, updatedBranch.TrackedBranch); + Assert.Equal(upstreamBranchName, updatedBranch.UpstreamBranchCanonicalName); + Assert.Equal(remoteName, updatedBranch.RemoteName); + } + } + + [Fact] + public void CanSetLocalTrackedBranch() + { + const string testBranchName = "branchToSetUpstreamInfoFor"; + const string localTrackedBranchName = "refs/heads/master"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Branch branch = repo.CreateBranch(testBranchName); + Branch trackedBranch = repo.Branches[localTrackedBranchName]; + Assert.False(trackedBranch.IsRemote); + + Branch branch = repo.CreateBranch(testBranchName, trackedBranch.Tip); Assert.False(branch.IsTracking); - Branch upstreamBranch = repo.Branches[upstreamBranchName]; - repo.Branches.Update(branch, - b => b.Upstream = upstreamBranch.CanonicalName); + b => b.TrackedBranch = trackedBranch.CanonicalName); // Get the updated branch information. branch = repo.Branches[testBranchName]; // 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); @@ -504,40 +843,46 @@ public void CanSetLocalUpstreamBranch() // Verify the IsTracking and TrackedBranch properties. Assert.True(branch.IsTracking); - Assert.Equal(upstreamBranch, branch.TrackedBranch); + Assert.Equal(trackedBranch, branch.TrackedBranch); + Assert.Equal("refs/heads/master", branch.UpstreamBranchCanonicalName); } } [Fact] - public void CanUnsetUpstreamBranch() + public void CanUnsetTrackedBranch() { const string testBranchName = "branchToSetUpstreamInfoFor"; - const string upstreamBranchName = "refs/remotes/origin/master"; + const string trackedBranchName = "refs/remotes/origin/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + 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.Upstream = upstreamBranchName); + b => b.TrackedBranch = trackedBranch.CanonicalName); // Got the updated branch from the Update() method Assert.True(branch.IsTracking); branch = repo.Branches.Update(branch, - b => b.Upstream = null); + b => b.TrackedBranch = null); // Verify this is no longer a tracking branch Assert.False(branch.IsTracking); + Assert.Null(branch.RemoteName); + Assert.Null(branch.UpstreamBranchCanonicalName); } } [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()); @@ -546,9 +891,8 @@ public void CanWalkCommitsFromBranch() private void AssertRemoval(string branchName, bool isRemote, bool shouldPreviouslyAssertExistence) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { if (shouldPreviouslyAssertExistence) { @@ -574,9 +918,8 @@ public void CanRemoveAnExistingNamedBranch(string branchName, bool isRemote) [InlineData("origin/br2")] public void CanRemoveAnExistingBranch(string branchName) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch curBranch = repo.Branches[branchName]; @@ -586,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)] @@ -597,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; @@ -635,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); @@ -645,8 +1010,8 @@ public void OnlyOneBranchIsTheHead() [Fact] public void TwoBranchesPointingAtTheSameCommitAreNotBothCurrent() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["refs/heads/master"]; @@ -656,44 +1021,62 @@ public void TwoBranchesPointingAtTheSameCommitAreNotBothCurrent() } [Fact] - public void CanMoveABranch() + public void CanRenameABranch() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + Assert.Null(repo.Branches["br3"]); + var br2 = repo.Branches["br2"]; + Assert.NotNull(br2); - Branch newBranch = repo.Branches.Move("br2", "br3"); - Assert.Equal("br3", newBranch.Name); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + Branch newBranch = repo.Branches.Rename("br2", "br3"); + + 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, + Constants.Identity, before); } } [Fact] - public void BlindlyMovingABranchOverAnExistingOneThrows() + public void BlindlyRenamingABranchOverAnExistingOneThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Branches.Move("br2", "test")); + Assert.Throws(() => repo.Branches.Rename("br2", "test")); } } [Fact] - public void CanMoveABranchWhileOverwritingAnExistingOne() + public void CanRenameABranchWhileOverwritingAnExistingOne() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { + EnableRefLog(repo); + Branch test = repo.Branches["test"]; Assert.NotNull(test); Branch br2 = repo.Branches["br2"]; Assert.NotNull(br2); - Branch newBranch = repo.Branches.Move("br2", "test", true); - Assert.Equal("test", newBranch.Name); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + Branch newBranch = repo.Branches.Rename("br2", "test", true); + Assert.Equal("test", newBranch.FriendlyName); Assert.Null(repo.Branches["br2"]); @@ -702,82 +1085,163 @@ public void CanMoveABranchWhileOverwritingAnExistingOne() Assert.Equal(newBranch, newTest); Assert.Equal(br2.Tip, newTest.Tip); + + AssertRefLogEntry(repo, newBranch.CanonicalName, + string.Format("branch: renamed {0} to {1}", br2.CanonicalName, newBranch.CanonicalName), + br2.Tip.Id, + newTest.Tip.Id, + Constants.Identity, before); } } [Fact] public void DetachedHeadIsNotATrackingBranch() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Reset(ResetOptions.Hard); + 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); + + Assert.NotNull(repo.Head.TrackingDetails); + Assert.Null(repo.Head.TrackingDetails.AheadBy); + Assert.Null(repo.Head.TrackingDetails.BehindBy); + Assert.Null(repo.Head.TrackingDetails.CommonAncestor); } } [Fact] public void TrackedBranchExistsFromDefaultConfigInEmptyClone() { - SelfCleaningDirectory scd1 = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(true); Uri uri; - using (var emptyRepo = Repository.Init(scd1.DirectoryPath, true)) + + using (var emptyRepo = new Repository(repoPath)) { - uri = new Uri(emptyRepo.Info.Path); + uri = new Uri($"file://{emptyRepo.Info.Path}"); } SelfCleaningDirectory scd2 = BuildSelfCleaningDirectory(); - using (Repository repo = Repository.Clone(uri.AbsoluteUri, scd2.RootedDirectoryPath)) + + string clonedRepoPath = Repository.Clone(uri.AbsoluteUri, scd2.DirectoryPath); + + 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); Assert.Null(repo.Head.TrackedBranch.Tip); - Assert.Null(repo.Head.AheadBy); - Assert.Null(repo.Head.BehindBy); + Assert.NotNull(repo.Head.TrackingDetails); + Assert.Null(repo.Head.TrackingDetails.AheadBy); + 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); - File.WriteAllText(Path.Combine(scd2.RootedDirectoryPath, "a.txt"), "a"); - repo.Index.Stage("a.txt"); - repo.Commit("A file", DummySignature, DummySignature); + Touch(repo.Info.WorkingDirectory, "a.txt", "a"); + Commands.Stage(repo, "a.txt"); + repo.Commit("A file", Constants.Signature, Constants.Signature); Assert.NotNull(repo.Head.Tip); Assert.NotNull(repo.Head.TrackedBranch); + Assert.Null(repo.Head.TrackedBranch.Tip); - Assert.Null(repo.Head.AheadBy); - Assert.Null(repo.Head.BehindBy); + Assert.NotNull(repo.Head.TrackingDetails); + Assert.Null(repo.Head.TrackingDetails.AheadBy); + Assert.Null(repo.Head.TrackingDetails.BehindBy); + Assert.Null(repo.Head.TrackingDetails.CommonAncestor); } } [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); - Assert.Null(branch.AheadBy); - Assert.Null(branch.BehindBy); + + Assert.NotNull(branch.TrackingDetails); + Assert.Null(branch.TrackingDetails.AheadBy); + Assert.Null(branch.TrackingDetails.BehindBy); + Assert.Null(branch.TrackingDetails.CommonAncestor); } } } + + private static T[] SortedBranches(IEnumerable branches, Func selector) + { + return branches.OrderBy(b => b.CanonicalName, StringComparer.Ordinal).Select(selector).ToArray(); + } + + [Fact] + public void CreatingABranchIncludesTheCorrectReflogEntries() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) + { + EnableRefLog(repo); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var branch = repo.Branches.Add("foo", repo.Head.Tip); + + AssertRefLogEntry(repo, branch.CanonicalName, + string.Format("branch: Created from {0}", repo.Head.Tip.Sha), + null, branch.Tip.Id, + Constants.Identity, before); + + before = DateTimeOffset.Now.TruncateMilliseconds(); + + branch = repo.Branches.Add("bar", repo.Head.Tip); + + AssertRefLogEntry(repo, branch.CanonicalName, + "branch: Created from " + repo.Head.Tip.Sha, + null, repo.Head.Tip.Id, + Constants.Identity, before); + } + } + + [Fact] + public void RenamingABranchIncludesTheCorrectReflogEntries() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) + { + EnableRefLog(repo); + var master = repo.Branches["master"]; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newMaster = repo.Branches.Rename(master, "new-master"); + AssertRefLogEntry(repo, newMaster.CanonicalName, "branch: renamed refs/heads/master to refs/heads/new-master", + newMaster.Tip.Id, newMaster.Tip.Id, + Constants.Identity, before); + + before = DateTimeOffset.Now.TruncateMilliseconds(); + + var newMaster2 = repo.Branches.Rename(newMaster, "new-master2"); + AssertRefLogEntry(repo, newMaster2.CanonicalName, "branch: renamed refs/heads/new-master to refs/heads/new-master2", + newMaster.Tip.Id, newMaster2.Tip.Id, + Constants.Identity, before); + } + } } } diff --git a/LibGit2Sharp.Tests/CheckoutFixture.cs b/LibGit2Sharp.Tests/CheckoutFixture.cs index b272cf1b3..045e20e1f 100644 --- a/LibGit2Sharp.Tests/CheckoutFixture.cs +++ b/LibGit2Sharp.Tests/CheckoutFixture.cs @@ -1,26 +1,26 @@ using System; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; -using System.IO; namespace LibGit2Sharp.Tests { public class CheckoutFixture : BaseFixture { - private static readonly string originalFilePath = "a.txt"; - private static readonly string originalFileContent = "Hello"; - private static readonly string alternateFileContent = "There again"; - private static readonly string otherBranchName = "other"; + private const string originalFilePath = "a.txt"; + private const string originalFileContent = "Hello"; + private const string alternateFileContent = "There again"; + private const string otherBranchName = "other"; [Theory] [InlineData("i-do-numbers")] [InlineData("diff-test-cases")] public void CanCheckoutAnExistingBranch(string branchName) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); @@ -28,13 +28,15 @@ public void CanCheckoutAnExistingBranch(string branchName) // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); Branch branch = repo.Branches[branchName]; Assert.NotNull(branch); + AssertBelongsToARepository(repo, branch); - Branch test = repo.Checkout(branch); + Branch test = Commands.Checkout(repo, branch); Assert.False(repo.Info.IsHeadDetached); + AssertBelongsToARepository(repo, test); Assert.False(test.IsRemote); Assert.True(test.IsCurrentRepositoryHead); @@ -43,7 +45,15 @@ public void CanCheckoutAnExistingBranch(string branchName) Assert.False(master.IsCurrentRepositoryHead); // Working directory should not be dirty - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); + + // Assert reflog entry is created + var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); + Assert.Equal(master.Tip.Id, reflogEntry.From); + Assert.Equal(branch.Tip.Id, reflogEntry.To); + Assert.NotNull(reflogEntry.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); + Assert.Equal(string.Format("checkout: moving from master to {0}", branchName), reflogEntry.Message); } } @@ -52,8 +62,8 @@ public void CanCheckoutAnExistingBranch(string branchName) [InlineData("diff-test-cases")] public void CanCheckoutAnExistingBranchByName(string branchName) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); @@ -61,9 +71,9 @@ public void CanCheckoutAnExistingBranchByName(string branchName) // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); - Branch test = repo.Checkout(branchName); + Branch test = Commands.Checkout(repo, branchName); Assert.False(repo.Info.IsHeadDetached); Assert.False(test.IsRemote); @@ -73,17 +83,29 @@ public void CanCheckoutAnExistingBranchByName(string branchName) Assert.False(master.IsCurrentRepositoryHead); // Working directory should not be dirty - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); + + // Assert reflog entry is created + var reflogEntry = repo.Refs.Log(repo.Refs.Head).First(); + Assert.Equal(master.Tip.Id, reflogEntry.From); + Assert.Equal(repo.Branches[branchName].Tip.Id, reflogEntry.To); + Assert.NotNull(reflogEntry.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); + Assert.Equal(string.Format("checkout: moving from master to {0}", branchName), reflogEntry.Message); } } [Theory] - [InlineData("6dcf9bf")] - [InlineData("refs/tags/lw")] - public void CanCheckoutAnArbitraryCommit(string commitPointer) + [InlineData("6dcf9bf", true, "6dcf9bf")] + [InlineData("refs/tags/lw", true, "lw")] + [InlineData("HEAD~2", true, "HEAD~2")] + [InlineData("6dcf9bf", false, "6dcf9bf7541ee10456529833502442f385010c3d")] + [InlineData("refs/tags/lw", false, "e90810b8df3e80c413d903f631643c716887138d")] + [InlineData("HEAD~2", false, "4c062a6361ae6959e06292c1fa5e2822d9c96345")] + public void CanCheckoutAnArbitraryCommit(string commitPointer, bool checkoutByCommitOrBranchSpec, string expectedReflogTarget) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); @@ -91,48 +113,59 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer) // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); - Branch detachedHead = repo.Checkout(commitPointer); + var commit = repo.Lookup(commitPointer); + AssertBelongsToARepository(repo, commit); + + Branch detachedHead = checkoutByCommitOrBranchSpec ? Commands.Checkout(repo, commitPointer) : Commands.Checkout(repo, commit); Assert.Equal(repo.Head, detachedHead); - Assert.Equal(repo.Lookup(commitPointer).Sha, detachedHead.Tip.Sha); + 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); Assert.False(master.IsCurrentRepositoryHead); + + // Assert reflog entry is created + 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.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); + Assert.Equal(string.Format("checkout: moving from master to {0}", expectedReflogTarget), reflogEntry.Message); } } [Fact] public void CheckoutAddsMissingFilesInWorkingDirectory() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // 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)); } } @@ -140,26 +173,27 @@ public void CheckoutAddsMissingFilesInWorkingDirectory() [Fact] public void CheckoutRemovesExtraFilesInWorkingDirectory() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Add extra file in master branch // Verify it is removed after checking out otherBranch. - string newFileFullPath = Path.Combine(repo.Info.WorkingDirectory, "b.txt"); - File.WriteAllText(newFileFullPath, "hello from master branch!\n"); - repo.Index.Stage(newFileFullPath); + string newFileFullPath = Touch( + repo.Info.WorkingDirectory, "b.txt", "hello from master branch!\n"); + + 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)); } } @@ -167,26 +201,27 @@ public void CheckoutRemovesExtraFilesInWorkingDirectory() [Fact] public void CheckoutUpdatesModifiedFilesInWorkingDirectory() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Modify file in master branch. // Verify contents match initial commit after checking out other branch. - string fullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - File.WriteAllText(fullPath, "Update : hello from master branch!\n"); - repo.Index.Stage(fullPath); + string fullPath = Touch( + repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); + + 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)); } } @@ -202,139 +237,181 @@ public void CanForcefullyCheckoutWithConflictingStagedChanges() // 4) Create conflicting change // 5) Forcefully checkout master - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - string fileFullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); // 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); // Add change to master. - string fullPath = Path.Combine(repo.Info.WorkingDirectory, fileFullPath); - File.WriteAllText(fullPath, originalFileContent); - repo.Index.Stage(fullPath); + Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); + + 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. - File.WriteAllText(fullPath, alternateFileContent); - repo.Index.Stage(fullPath); + Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); + 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, CheckoutOptions.Force, null); + 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); } } [Fact] public void CheckingOutWithMergeConflictsThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - string fullPath = Path.Combine(repo.Info.WorkingDirectory, "a.txt"); - File.WriteAllText(fullPath, "Hello\n"); - repo.Index.Stage(fullPath); + Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello\n"); + Commands.Stage(repo, originalFilePath); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); // Create 2nd branch repo.CreateBranch("branch2"); // Update file in main - File.WriteAllText(fullPath, "Hello from master!\n"); - repo.Index.Stage(fullPath); + Touch(repo.Info.WorkingDirectory, originalFilePath, "Hello from master!\n"); + Commands.Stage(repo, originalFilePath); repo.Commit("2nd commit", Constants.Signature, Constants.Signature); // Checkout branch2 - repo.Checkout("branch2"); - File.WriteAllText(fullPath, "Hello From branch2!\n"); + 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(fullPath); - Assert.Throws(() => repo.Checkout("master")); + Commands.Stage(repo, originalFilePath); + Assert.Throws(() => Commands.Checkout(repo, "master")); + } + } + + [Fact] + public void CanCancelCheckoutThroughNotifyCallback() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + const string relativePath = "a.txt"; + Touch(repo.Info.WorkingDirectory, relativePath, "Hello\n"); + + Commands.Stage(repo, relativePath); + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + + // Create 2nd branch + repo.CreateBranch("branch2"); + + // Update file in main + Touch(repo.Info.WorkingDirectory, relativePath, "Hello from master!\n"); + Commands.Stage(repo, relativePath); + repo.Commit("2nd commit", Constants.Signature, Constants.Signature); + + // 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"); + + // Verify that we get called for the notify conflict cb + string conflictPath = string.Empty; + + CheckoutOptions options = new CheckoutOptions() + { + OnCheckoutNotify = (path, flags) => { conflictPath = path; return false; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Conflict, + }; + + Assert.Throws(() => Commands.Checkout(repo, "master", options)); + Assert.Equal(relativePath, conflictPath); } } [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")); } } [Fact] public void CheckingOutAgainstAnUnbornBranchThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Assert.True(repo.Info.IsHeadOrphaned); + 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))); } } [Fact] public void CheckingOutThroughBranchCallsCheckoutProgress() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); bool wasCalled = false; Branch branch = repo.Branches[otherBranchName]; - branch.Checkout(CheckoutOptions.None, (path, completed, total) => wasCalled = true); + Commands.Checkout(repo, branch, + new CheckoutOptions { OnCheckoutProgress = (path, completed, total) => wasCalled = true }); Assert.True(wasCalled); } @@ -343,147 +420,219 @@ public void CheckingOutThroughBranchCallsCheckoutProgress() [Fact] public void CheckingOutThroughRepositoryCallsCheckoutProgress() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); bool wasCalled = false; - repo.Checkout(otherBranchName, CheckoutOptions.None, (path, completed, total) => wasCalled = true); + Commands.Checkout(repo, otherBranchName, new CheckoutOptions() { OnCheckoutProgress = (path, completed, total) => wasCalled = true }); Assert.True(wasCalled); } } + [Theory] + [InlineData(CheckoutNotifyFlags.Conflict, "conflict.txt", false)] + [InlineData(CheckoutNotifyFlags.Updated, "updated.txt", false)] + [InlineData(CheckoutNotifyFlags.Untracked, "untracked.txt", false)] + [InlineData(CheckoutNotifyFlags.Ignored, "bin", true)] + public void CheckingOutCallsCheckoutNotify(CheckoutNotifyFlags notifyFlags, string expectedNotificationPath, bool isDirectory) + { + if (isDirectory) + { + expectedNotificationPath = expectedNotificationPath + Path.DirectorySeparatorChar; + } + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + PopulateBasicRepository(repo); + + const string relativePathUpdated = "updated.txt"; + Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text A"); + Commands.Stage(repo, relativePathUpdated); + repo.Commit("Commit initial update file", Constants.Signature, Constants.Signature); + + // Create conflicting change + const string relativePathConflict = "conflict.txt"; + Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text A"); + Commands.Stage(repo, relativePathConflict); + repo.Commit("Initial commit of conflict.txt and update.txt", Constants.Signature, Constants.Signature); + + // Create another branch + repo.CreateBranch("newbranch"); + + // Make an edit to conflict.txt and update.txt + Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text BB"); + Commands.Stage(repo, relativePathUpdated); + Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text BB"); + Commands.Stage(repo, relativePathConflict); + + repo.Commit("2nd commit of conflict.txt and update.txt on master branch", Constants.Signature, Constants.Signature); + + // Checkout other branch + Commands.Checkout(repo, "newbranch"); + + // Make alternate edits to conflict.txt and update.txt + Touch(repo.Info.WorkingDirectory, relativePathUpdated, "updated file text CCC"); + Commands.Stage(repo, relativePathUpdated); + Touch(repo.Info.WorkingDirectory, relativePathConflict, "conflict file text CCC"); + 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"); + Commands.Stage(repo, relativePathConflict); + + // Create ignored change + string relativePathIgnore = Path.Combine("bin", "ignored.txt"); + Touch(repo.Info.WorkingDirectory, relativePathIgnore, "ignored file"); + + // Create untracked change + const string relativePathUntracked = "untracked.txt"; + Touch(repo.Info.WorkingDirectory, relativePathUntracked, "untracked file"); + + bool wasCalled = false; + string actualNotificationPath = string.Empty; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + CheckoutOptions options = new CheckoutOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotificationPath = path; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = notifyFlags, + }; + + Assert.Throws(() => Commands.Checkout(repo, "master", options)); + + Assert.True(wasCalled); + Assert.Equal(expectedNotificationPath, actualNotificationPath); + Assert.Equal(notifyFlags, actualNotifyFlags); + } + } + [Fact] public void CheckoutRetainsUntrackedChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Generate an unstaged change. - string fullPathFileB = Path.Combine(repo.Info.WorkingDirectory, "b.txt"); - File.WriteAllText(fullPathFileB, alternateFileContent); + 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)); } } [Fact] public void ForceCheckoutRetainsUntrackedChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Generate an unstaged change. - string fullPathFileB = Path.Combine(repo.Info.WorkingDirectory, "b.txt"); - File.WriteAllText(fullPathFileB, alternateFileContent); + 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, CheckoutOptions.Force, null); + 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)); } } [Fact] public void CheckoutRetainsUnstagedChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Generate an unstaged change. - string fullPathFileA = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - File.WriteAllText(fullPathFileA, alternateFileContent); + 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)); } } [Fact] public void CheckoutRetainsStagedChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); // Generate a staged change. - string fullPathFileA = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - File.WriteAllText(fullPathFileA, alternateFileContent); - repo.Index.Stage(fullPathFileA); + string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, alternateFileContent); + 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)); } } [Fact] public void CheckoutRetainsIgnoredChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); - // Create a bin directory. - string ignoredDirectoryPath = Path.Combine(repo.Info.WorkingDirectory, "bin"); - Directory.CreateDirectory(ignoredDirectoryPath); - // Create file in ignored bin directory. - string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, Path.Combine("bin", "some_ignored_file.txt")); - File.WriteAllText(ignoredFilePath, "hello from this ignored file."); + string ignoredFilePath = Touch( + repo.Info.WorkingDirectory, + "bin/some_ignored_file.txt", + "hello from this ignored file."); - // The following check does not report ignored entries... - // TODO: Uncomment once libgit2/libgit2#1251 is merged - // 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)); } } @@ -491,30 +640,26 @@ public void CheckoutRetainsIgnoredChanges() [Fact] public void ForceCheckoutRetainsIgnoredChanges() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); - // Create a bin directory. - string ignoredDirectoryPath = Path.Combine(repo.Info.WorkingDirectory, "bin"); - Directory.CreateDirectory(ignoredDirectoryPath); - // Create file in ignored bin directory. - string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, Path.Combine("bin", "some_ignored_file.txt")); - File.WriteAllText(ignoredFilePath, "hello from this ignored file."); + string ignoredFilePath = Touch( + repo.Info.WorkingDirectory, + "bin/some_ignored_file.txt", + "hello from this ignored file."); - // The following check does not report ignored entries... - // TODO: Uncomment once libgit2/libgit2#1251 is merged - // 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, CheckoutOptions.Force, null); + 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)); } } @@ -522,9 +667,9 @@ public void ForceCheckoutRetainsIgnoredChanges() [Fact] public void CheckoutBranchSnapshot() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { PopulateBasicRepository(repo); @@ -534,18 +679,17 @@ public void CheckoutBranchSnapshot() Commit initialCommit = initial.Tip; // Add commit to master - string fullPath = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - File.WriteAllText(fullPath, "Update : hello from master branch!\n"); - repo.Index.Stage(fullPath); + string fullPath = Touch(repo.Info.WorkingDirectory, originalFilePath, "Update : hello from master branch!\n"); + 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); @@ -553,11 +697,14 @@ public void CheckoutBranchSnapshot() } } - [Fact] - public void CheckingOutRemoteBranchResultsInDetachedHead() + [Theory] + [InlineData("refs/remotes/origin/master")] + [InlineData("master@{u}")] + [InlineData("origin/master")] + public void CheckingOutRemoteBranchResultsInDetachedHead(string remoteBranchSpec) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); @@ -565,7 +712,7 @@ public void CheckingOutRemoteBranchResultsInDetachedHead() // Set the working directory to the current head ResetAndCleanWorkingDirectory(repo); - repo.Checkout("refs/remotes/origin/master"); + Commands.Checkout(repo, remoteBranchSpec); // Verify that HEAD is detached. Assert.Equal(repo.Refs["HEAD"].TargetIdentifier, repo.Branches["origin/master"].Tip.Sha); @@ -576,8 +723,8 @@ public void CheckingOutRemoteBranchResultsInDetachedHead() [Fact] public void CheckingOutABranchDoesNotAlterBinaryFiles() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // $ git hash-object square-logo.png // b758c5bc1c8117c2a4c545dae2903e36360501c5 @@ -586,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", CheckoutOptions.Force, null); + 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); @@ -599,21 +746,347 @@ public void CheckingOutABranchDoesNotAlterBinaryFiles() } } + [Theory] + [InlineData("a447ba2ca8")] + [InlineData("lw")] + [InlineData("e90810^{}")] + public void CheckoutFromDetachedHead(string commitPointer) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.RetrieveStatus().IsDirty); + + var commitSha = repo.Lookup(commitPointer).Sha; + + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); + + 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.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); + Assert.Equal(string.Format("checkout: moving from {0} to {1}", initialHead.Tip.Sha, commitPointer), reflogEntry.Message); + } + } + + [Fact] + public void CheckoutBranchFromDetachedHead() + { + 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.RetrieveStatus().IsDirty); + + Branch initialHead = Commands.Checkout(repo, "6dcf9bf"); + + Assert.True(repo.Info.IsHeadDetached); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + Branch newHead = Commands.Checkout(repo, repo.Branches["master"]); + + // Assert reflog entry is created + 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); + } + } + + [Theory] + [InlineData("master", "refs/heads/master")] + [InlineData("heads/master", "refs/heads/master")] + public void CheckoutBranchByShortNameAttachesTheHead(string shortBranchName, string referenceName) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.RetrieveStatus().IsDirty); + + Commands.Checkout(repo, "6dcf9bf"); + Assert.True(repo.Info.IsHeadDetached); + + var branch = Commands.Checkout(repo, shortBranchName); + + Assert.False(repo.Info.IsHeadDetached); + Assert.Equal(referenceName, repo.Head.CanonicalName); + Assert.Equal(referenceName, branch.CanonicalName); + } + } + + [Fact] + public void CheckoutPreviousCheckedOutBranch() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.RetrieveStatus().IsDirty); + + Branch previousHead = Commands.Checkout(repo, "i-do-numbers"); + Commands.Checkout(repo, "diff-test-cases"); + + //Go back to previous branch checked out + var branch = Commands.Checkout(repo, @"@{-1}"); + + Assert.False(repo.Info.IsHeadDetached); + Assert.Equal(previousHead.CanonicalName, repo.Head.CanonicalName); + Assert.Equal(previousHead.CanonicalName, branch.CanonicalName); + } + } + + [Fact] + public void CheckoutCurrentReference() + { + 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.RetrieveStatus().IsDirty); + + var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); + + // Checkout branch + Commands.Checkout(repo, master); + + Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + // Checkout in detached mode + Commands.Checkout(repo, master.Tip.Sha); + + Assert.True(repo.Info.IsHeadDetached); + 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(); + + Commands.Checkout(repo, repo.Head); + + Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + + // Checkout attached "HEAD" => nothing should happen + Commands.Checkout(repo, "master"); + reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); + + Commands.Checkout(repo, repo.Head); + + Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + + Commands.Checkout(repo, "HEAD"); + + Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); + } + } + + [Fact] + public void CheckoutLowerCasedHeadThrows() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => Commands.Checkout(repo, "head")); + } + } + + [Fact] + public void CanCheckoutAttachedHead() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.False(repo.Info.IsHeadDetached); + + Commands.Checkout(repo, repo.Head); + Assert.False(repo.Info.IsHeadDetached); + + Commands.Checkout(repo, "HEAD"); + Assert.False(repo.Info.IsHeadDetached); + } + } + + [Fact] + public void CanCheckoutDetachedHead() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Commands.Checkout(repo, repo.Head.Tip.Sha); + + Assert.True(repo.Info.IsHeadDetached); + + Commands.Checkout(repo, repo.Head); + Assert.True(repo.Info.IsHeadDetached); + + Commands.Checkout(repo, "HEAD"); + Assert.True(repo.Info.IsHeadDetached); + } + } + + [Theory] + [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 = SandboxStandardTestRepo(); + using (var repo = new Repository(repoPath)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + + Commands.Checkout(repo, originalBranch); + Assert.False(repo.RetrieveStatus().IsDirty); + + repo.CheckoutPaths(checkoutFrom, new[] { path }); + + Assert.Equal(expectedStatus, repo.RetrieveStatus(path)); + Assert.Single(repo.RetrieveStatus()); + } + } + + [Fact] + public void CanCheckoutPaths() + { + 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.RetrieveStatus().IsDirty); + + repo.CheckoutPaths("i-do-numbers", checkoutPaths); + + foreach (string checkoutPath in checkoutPaths) + { + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(checkoutPath)); + } + } + } + + [Fact] + public void CannotCheckoutPathsWithEmptyOrNullPathArgument() + { + string repoPath = SandboxStandardTestRepo(); + + using (var repo = new Repository(repoPath)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.RetrieveStatus().IsDirty); + + // Passing null 'paths' parameter should throw + Assert.Throws(() => repo.CheckoutPaths("i-do-numbers", null)); + + // Passing empty list should do nothing + repo.CheckoutPaths("i-do-numbers", Enumerable.Empty()); + Assert.False(repo.RetrieveStatus().IsDirty); + } + } + + [Theory] + [InlineData("new.txt")] + [InlineData("1.txt")] + public void CanCheckoutPathFromCurrentBranch(string fileName) + { + string repoPath = SandboxStandardTestRepo(); + + using (var repo = new Repository(repoPath)) + { + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + + Assert.False(repo.RetrieveStatus().IsDirty); + + Touch(repo.Info.WorkingDirectory, fileName, "new text file"); + + Assert.True(repo.RetrieveStatus().IsDirty); + + var opts = new CheckoutOptions { CheckoutModifiers = CheckoutModifiers.Force }; + repo.CheckoutPaths("HEAD", new[] { fileName }, opts); + + 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)); + } + } + /// - /// Helper method to populate a simple repository with - /// a single file and two branches. + /// Helper method to populate a simple repository with + /// a single file and two branches. /// /// Repository to populate - private void PopulateBasicRepository(Repository repo) + private void PopulateBasicRepository(IRepository repo) { // Generate a .gitignore file. - string gitIgnoreFilePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(gitIgnoreFilePath, "bin"); - repo.Index.Stage(gitIgnoreFilePath); + string gitIgnoreFilePath = Touch(repo.Info.WorkingDirectory, ".gitignore", "bin"); + Commands.Stage(repo, gitIgnoreFilePath); - string fullPathFileA = Path.Combine(repo.Info.WorkingDirectory, originalFilePath); - File.WriteAllText(fullPathFileA, originalFileContent); - repo.Index.Stage(fullPathFileA); + string fullPathFileA = Touch(repo.Info.WorkingDirectory, originalFilePath, originalFileContent); + Commands.Stage(repo, fullPathFileA); repo.Commit("Initial commit", Constants.Signature, Constants.Signature); @@ -625,10 +1098,10 @@ private void PopulateBasicRepository(Repository repo) /// working directory matches the current Head commit. /// /// Repository whose current working directory should be operated on. - private void ResetAndCleanWorkingDirectory(Repository repo) + private void ResetAndCleanWorkingDirectory(IRepository repo) { // Reset the index and the working tree. - repo.Reset(ResetOptions.Hard); + repo.Reset(ResetMode.Hard); // Clean the working directory. repo.RemoveUntrackedFiles(); diff --git a/LibGit2Sharp.Tests/CherryPickFixture.cs b/LibGit2Sharp.Tests/CherryPickFixture.cs new file mode 100644 index 000000000..f4a383fef --- /dev/null +++ b/LibGit2Sharp.Tests/CherryPickFixture.cs @@ -0,0 +1,227 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class CherryPickFixture : BaseFixture + { + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanCherryPick(bool fromDetachedHead) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + if (fromDetachedHead) + { + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); + } + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + CherryPickResult result = repo.CherryPick(commitToMerge, Constants.Signature); + + Assert.Equal(CherryPickStatus.CherryPicked, result.Status); + Assert.Equal(cherryPickedCommitId, result.Commit.Id.Sha); + 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); + } + } + + [Fact] + public void CherryPickWithConflictDoesNotCommit() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + 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"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch + + 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 + + CherryPickResult cherryPickResult = repo.CherryPick(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(CherryPickStatus.Conflicts, cherryPickResult.Status); + + Assert.Null(cherryPickResult.Commit); + 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)); + + Assert.False(changes.IsBinaryComparison); + } + } + + [Theory] + [InlineData(CheckoutFileConflictStrategy.Ours)] + [InlineData(CheckoutFileConflictStrategy.Theirs)] + public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflictStrategy) + { + const string conflictFile = "a.txt"; + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + CherryPickOptions cherryPickOptions = new CherryPickOptions() + { + FileConflictStrategy = conflictStrategy, + }; + + CherryPickResult result = repo.CherryPick(branch.Tip, Constants.Signature, cherryPickOptions); + Assert.Equal(CherryPickStatus.Conflicts, result.Status); + + // Get the information on the conflict. + Conflict conflict = repo.Index.Conflicts[conflictFile]; + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch (conflictStrategy) + { + case CheckoutFileConflictStrategy.Theirs: + expectedBlob = repo.Lookup(conflict.Theirs.Id); + break; + case CheckoutFileConflictStrategy.Ours: + expectedBlob = repo.Lookup(conflict.Ours.Id); + break; + default: + throw new Exception("Unexpected FileConflictStrategy"); + } + + Assert.NotNull(expectedBlob); + + // 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 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); + + Commands.Stage(repository, filename); + + return repository.Commit("New commit", Constants.Signature, Constants.Signature); + } + + // Commit IDs of the checked in merge_testrepo + private const string cherryPickedCommitId = "74b37f366b6e1c682c1c9fe0c6b006cbe909cf91"; + } +} diff --git a/LibGit2Sharp.Tests/CleanFixture.cs b/LibGit2Sharp.Tests/CleanFixture.cs index d83ceb656..39c7a6152 100644 --- a/LibGit2Sharp.Tests/CleanFixture.cs +++ b/LibGit2Sharp.Tests/CleanFixture.cs @@ -9,25 +9,26 @@ public class CleanFixture : BaseFixture [Fact] public void CanCleanWorkingDirectory() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + 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 0f1350604..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,12 +13,13 @@ 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(); - using (Repository repo = Repository.Clone(url, scd.RootedDirectoryPath)) + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + using (var repo = new Repository(clonedRepoPath)) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); @@ -28,33 +30,84 @@ 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()); } } - private void AssertLocalClone(string path) + [Theory] + [InlineData("br2", "a4a7dce85cf63874e984719f4fdd239f5145052f")] + [InlineData("packed", "41bc8c69075bbdb46c5c6f0566cc8cc5b46e8bd9")] + [InlineData("test", "e90810b8df3e80c413d903f631643c716887138d")] + public void CanCloneWithCheckoutBranchName(string branchName, string headTipId) { var scd = BuildSelfCleaningDirectory(); - using (Repository clonedRepo = Repository.Clone(path, scd.RootedDirectoryPath)) - using (var originalRepo = new Repository(BareTestRepoPath)) + + 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); + } + } + + private void AssertLocalClone(string url, string path = null, bool isCloningAnEmptyRepository = false) + { + var scd = BuildSelfCleaningDirectory(); + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + using (var clonedRepo = new Repository(clonedRepoPath)) + using (var originalRepo = new Repository(path ?? url)) { 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(1, 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); - AssertLocalClone(uri.AbsoluteUri); + var uri = new Uri($"file://{Path.GetFullPath(BareTestRepoPath)}"); + AssertLocalClone(uri.AbsoluteUri, BareTestRepoPath); } [Fact] @@ -63,15 +116,28 @@ public void CanCloneALocalRepositoryFromAStandardPath() AssertLocalClone(BareTestRepoPath); } + [Fact] + public void CanCloneALocalRepositoryFromANewlyCreatedTemporaryPath() + { + var path = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(path); + Repository.Init(scd.DirectoryPath); + AssertLocalClone(scd.DirectoryPath, isCloningAnEmptyRepository: true); + } + [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(); - using (Repository repo = Repository.Clone(url, scd.RootedDirectoryPath, bare: true)) + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions + { + IsBare = true + }); + + using (var repo = new Repository(clonedRepoPath)) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); @@ -84,31 +150,49 @@ 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(); - using (Repository repo = Repository.Clone(url, scd.RootedDirectoryPath, checkout: false)) + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath, new CloneOptions() + { + Checkout = false + }); + + using (var repo = new Repository(clonedRepoPath)) { - Assert.False(File.Exists(Path.Combine(scd.RootedDirectoryPath, "master.txt"))); + Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, "master.txt"))); } } [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(); - using (Repository repo = Repository.Clone(url, scd.RootedDirectoryPath, - onTransferProgress: (_) => transferWasCalled = true, - onCheckoutProgress: (a, b, c) => checkoutWasCalled = true)) + + Repository.Clone(url, scd.DirectoryPath, new CloneOptions() { - Assert.True(transferWasCalled); - Assert.True(checkoutWasCalled); - } + 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); } [SkippableFact] @@ -118,13 +202,15 @@ public void CanCloneWithCredentials() "Populate Constants.PrivateRepo* to run this test"); var scd = BuildSelfCleaningDirectory(); - using (Repository repo = Repository.Clone( - Constants.PrivateRepoUrl, scd.RootedDirectoryPath, - credentials: new Credentials - { - Username = Constants.PrivateRepoUsername, - Password = Constants.PrivateRepoPassword - })) + + string clonedRepoPath = Repository.Clone(Constants.PrivateRepoUrl, scd.DirectoryPath, + new CloneOptions() + { + FetchOptions = { CredentialsProvider = Constants.PrivateRepoCredentials } + }); + + + using (var repo = new Repository(clonedRepoPath)) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); @@ -135,5 +221,413 @@ public void CanCloneWithCredentials() Assert.False(repo.Info.IsBare); } } + + 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://github.com/libgit2/TestGitRepository")] + public void CloningWithoutWorkdirPathThrows(string url) + { + Assert.Throws(() => Repository.Clone(url, null)); + } + + [Fact] + public void CloningWithoutUrlThrows() + { + var scd = BuildSelfCleaningDirectory(); + + 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, + }); + + expectedCallbackInfo.Add(Path.Combine(workDirPath, relativeSubmodulePath), new CloneCallbackInfo() + { + RecursionDepth = 1, + RemoteUrl = expectedSubmoduleUrl, + StartingWorkInRepositoryCalled = true, + FinishedWorkInRepositoryCalled = true, + CheckoutProgressCalled = true, + RemoteRefUpdateCalled = true, + }); + + // 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 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 }; + + 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 af408ad67..c752f7415 100644 --- a/LibGit2Sharp.Tests/CommitAncestorFixture.cs +++ b/LibGit2Sharp.Tests/CommitAncestorFixture.cs @@ -2,6 +2,7 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -28,135 +29,106 @@ public class CommitAncestorFixture : BaseFixture * */ - [Fact] - public void CanFindCommonAncestorForTwoCommits() + [Theory] + [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", "c47800c", "9fd738e")] + [InlineData("9fd738e8f7967c078dceed8190330fc8648ee56a", "be3563a", "9fd738e")] + [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 = repo.Lookup("c47800c7266a2be04c571c04d5a6614691ea99bd"); - var second = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); - - Commit ancestor = repo.Commits.FindCommonAncestor(first, second); - - Assert.NotNull(ancestor); - Assert.Equal("5b5b025afb0b4c913b4c338a42934a3863bf3644", ancestor.Id.Sha); - } - } - - [Fact] - public void CanFindCommonAncestorForTwoCommitsAsEnumerable() - { - using (var repo = new Repository(BareTestRepoPath)) - { - var first = repo.Lookup("c47800c7266a2be04c571c04d5a6614691ea99bd"); - var second = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); - - Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second }); - - Assert.NotNull(ancestor); - Assert.Equal("5b5b025afb0b4c913b4c338a42934a3863bf3644", ancestor.Id.Sha); + var first = sha1 == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha1); + var second = sha2 == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha2); + + Commit ancestor = repo.ObjectDatabase.FindMergeBase(first, second); + + if (result == null) + { + Assert.Null(ancestor); + } + else + { + Assert.NotNull(ancestor); + Assert.Equal(result, ancestor.Id.Sha); + } } } - [Fact] - public void CanFindCommonAncestorForSeveralCommits() + [Theory] + [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", new[] { "c47800c", "9fd738e" }, MergeBaseFindingStrategy.Octopus)] + [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", new[] { "c47800c", "9fd738e" }, MergeBaseFindingStrategy.Standard)] + [InlineData("5b5b025afb0b4c913b4c338a42934a3863bf3644", new[] { "4c062a6", "be3563a", "c47800c", "5b5b025" }, MergeBaseFindingStrategy.Octopus)] + [InlineData("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", new[] { "4c062a6", "be3563a", "c47800c", "5b5b025" }, MergeBaseFindingStrategy.Standard)] + [InlineData(null, new[] { "4c062a6", "be3563a", "-", "c47800c", "5b5b025" }, MergeBaseFindingStrategy.Octopus)] + [InlineData("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", new[] { "4c062a6", "be3563a", "-", "c47800c", "5b5b025" }, MergeBaseFindingStrategy.Standard)] + [InlineData(null, new[] { "4c062a6", "-" }, MergeBaseFindingStrategy.Octopus)] + [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 first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); - var second = repo.Lookup("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - var third = repo.Lookup("c47800c7266a2be04c571c04d5a6614691ea99bd"); - var fourth = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); - - Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second, third, fourth }); - - Assert.NotNull(ancestor); - Assert.Equal("5b5b025afb0b4c913b4c338a42934a3863bf3644", ancestor.Id.Sha); + var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); + + Commit ancestor = repo.ObjectDatabase.FindMergeBase(commits, strategy); + + if (result == null) + { + Assert.Null(ancestor); + } + else + { + Assert.NotNull(ancestor); + Assert.Equal(result, ancestor.Id.Sha); + } } } - [Fact] - public void CannotFindAncestorForTwoCommmitsWithoutCommonAncestor() + [Theory] + [InlineData("4c062a6", "0000000")] + [InlineData("0000000", "4c062a6")] + public void FindCommonAncestorForTwoCommitsThrows(string sha1, string sha2) { - var scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - var first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); - var second = repo.Lookup("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - var third = repo.Lookup("c47800c7266a2be04c571c04d5a6614691ea99bd"); - var fourth = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); + var first = repo.Lookup(sha1); + var second = repo.Lookup(sha2); - Commit orphanedCommit = CreateOrphanedCommit(repo); - - Commit ancestor = repo.Commits.FindCommonAncestor(new[] { first, second, orphanedCommit, third, fourth }); - Assert.Null(ancestor); + Assert.Throws(() => repo.ObjectDatabase.FindMergeBase(first, second)); } } - [Fact] - public void CannotFindCommonAncestorForSeveralCommmitsWithoutCommonAncestor() + [Theory] + [InlineData(new[] { "4c062a6" }, MergeBaseFindingStrategy.Octopus)] + [InlineData(new[] { "4c062a6" }, MergeBaseFindingStrategy.Standard)] + [InlineData(new[] { "4c062a6", "be3563a", "000000" }, MergeBaseFindingStrategy.Octopus)] + [InlineData(new[] { "4c062a6", "be3563a", "000000" }, MergeBaseFindingStrategy.Standard)] + public void FindCommonAncestorForCommitsAsEnumerableThrows(string[] shas, MergeBaseFindingStrategy strategy) { - var scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - var first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); + var commits = shas.Select(sha => sha == "-" ? CreateOrphanedCommit(repo) : repo.Lookup(sha)).ToArray(); - var orphanedCommit = CreateOrphanedCommit(repo); - - Commit ancestor = repo.Commits.FindCommonAncestor(first, orphanedCommit); - Assert.Null(ancestor); + Assert.Throws(() => repo.ObjectDatabase.FindMergeBase(commits, strategy)); } } - private static Commit CreateOrphanedCommit(Repository repo) + private static Commit CreateOrphanedCommit(IRepository repo) { Commit random = repo.Head.Tip; Commit orphanedCommit = repo.ObjectDatabase.CreateCommit( - "This is a test commit created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'", random.Author, random.Committer, + "This is a test commit created by 'CommitFixture.CannotFindCommonAncestorForCommmitsWithoutCommonAncestor'", random.Tree, - Enumerable.Empty()); + Enumerable.Empty(), + false); return orphanedCommit; } - - [Fact] - public void FindCommonAncestorForSingleCommitThrows() - { - using (var repo = new Repository(BareTestRepoPath)) - { - var first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); - - Assert.Throws(() => repo.Commits.FindCommonAncestor(new[] { first })); - } - } - - [Fact] - public void FindCommonAncestorForEnumerableWithNullCommitThrows() - { - using (var repo = new Repository(BareTestRepoPath)) - { - var first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); - var second = repo.Lookup("be3563ae3f795b2b4353bcce3a527ad0a4f7f644"); - - Assert.Throws(() => repo.Commits.FindCommonAncestor(new[] { first, second, null })); - } - } - - [Fact] - public void FindCommonAncestorForWithNullCommitThrows() - { - using (var repo = new Repository(BareTestRepoPath)) - { - var first = repo.Lookup("4c062a6361ae6959e06292c1fa5e2822d9c96345"); - - Assert.Throws(() => repo.Commits.FindCommonAncestor(first, null)); - Assert.Throws(() => repo.Commits.FindCommonAncestor(null, first)); - } - } } } diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index 268d7ca99..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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { // Hard reset and then remove untracked files - repo.Reset(ResetOptions.Hard); + 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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,9 +77,10 @@ public void CanEnumerateCommitsInDetachedHeadState() [Fact] public void DefaultOrderingWhenEnumeratingCommitsIsTimeBased() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(GitSortOptions.Time, repo.Commits.SortedBy); + 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 Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) + foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f" })) { Assert.NotNull(commit); count++; @@ -99,35 +103,37 @@ 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 Filter { Since = Constants.UnknownSha }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new Filter { Since = "refs/heads/deadbeef" }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new Filter { 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { CreateCorruptedDeadBeefHead(repo.Info.Path); - Assert.Throws(() => repo.Commits.QueryBy(new Filter { Since = repo.Branches["deadbeef"] }).Count()); - Assert.Throws(() => repo.Commits.QueryBy(new Filter { 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 Filter { Since = string.Empty })); - Assert.Throws(() => repo.Commits.QueryBy(new Filter { Since = null })); - Assert.Throws(() => repo.Commits.QueryBy(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,12 +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 Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Time | GitSortOptions.Reverse })) + foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse + })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(reversedShas[count])); + Assert.StartsWith(reversedShas[count], commit.Sha); count++; } } @@ -153,9 +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 Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Time | GitSortOptions.Reverse }).ToList(); + List commits = repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time | CommitSortStrategies.Reverse + }).ToList(); foreach (Commit commit in commits) { Assert.NotNull(commit); @@ -168,12 +184,25 @@ public void CanEnumerateCommitsWithReverseTopoSorting() } } + [Fact] + public void CanSimplifyByFirstParent() + { + AssertEnumerationOfCommits( + repo => new CommitFilter { IncludeReachableFrom = repo.Head, FirstParentOnly = true }, + new[] + { + "4c062a6", "be3563a", "9fd738e", + "4a202b3", "5b5b025", "8496071", + }); + } + [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); } } @@ -181,12 +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 Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Time })) + foreach (Commit commit in repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Time + })) { Assert.NotNull(commit); - Assert.True(commit.Sha.StartsWith(expectedShas[count])); + Assert.StartsWith(expectedShas[count], commit.Sha); count++; } } @@ -196,9 +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 Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Topological }).ToList(); + List commits = repo.Commits.QueryBy(new CommitFilter + { + IncludeReachableFrom = "a4a7dce85cf63874e984719f4fdd239f5145052f", + SortBy = CommitSortStrategies.Topological + }).ToList(); foreach (Commit commit in commits) { Assert.NotNull(commit); @@ -215,7 +254,7 @@ public void CanEnumerateCommitsWithTopoSorting() public void CanEnumerateFromHead() { AssertEnumerationOfCommits( - repo => new Filter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "4c062a6", "be3563a", "c47800c", "9fd738e", @@ -226,18 +265,18 @@ public void CanEnumerateFromHead() [Fact] public void CanEnumerateFromDetachedHead() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repoClone = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repoClone = new Repository(path)) { // Hard reset and then remove untracked files - repoClone.Reset(ResetOptions.Hard); + repoClone.Reset(ResetMode.Hard); repoClone.RemoveUntrackedFiles(); string headSha = repoClone.Head.Tip.Sha; - repoClone.Checkout(headSha); + Commands.Checkout(repoClone, headSha); AssertEnumerationOfCommitsInRepo(repoClone, - repo => new Filter { Since = repo.Head }, + repo => new CommitFilter { IncludeReachableFrom = repo.Head }, new[] { "32eab9c", "592d3c8", "4c062a6", @@ -251,7 +290,7 @@ public void CanEnumerateFromDetachedHead() public void CanEnumerateUsingTwoHeadsAsBoundaries() { AssertEnumerationOfCommits( - repo => new Filter { Since = "HEAD", Until = "refs/heads/br2" }, + repo => new CommitFilter { IncludeReachableFrom = "HEAD", ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -260,7 +299,7 @@ public void CanEnumerateUsingTwoHeadsAsBoundaries() public void CanEnumerateUsingImplicitHeadAsSinceBoundary() { AssertEnumerationOfCommits( - repo => new Filter { Until = "refs/heads/br2" }, + repo => new CommitFilter { ExcludeReachableFrom = "refs/heads/br2" }, new[] { "4c062a6", "be3563a" } ); } @@ -269,7 +308,7 @@ public void CanEnumerateUsingImplicitHeadAsSinceBoundary() public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() { AssertEnumerationOfCommits( - repo => new Filter { Since = "a4a7dce", Until = "4a202b3" }, + repo => new CommitFilter { IncludeReachableFrom = "a4a7dce", ExcludeReachableFrom = "4a202b3" }, new[] { "a4a7dce", "c47800c", "9fd738e" } ); } @@ -278,7 +317,7 @@ public void CanEnumerateUsingTwoAbbreviatedShasAsBoundaries() public void CanEnumerateCommitsFromTwoHeads() { AssertEnumerationOfCommits( - repo => new Filter { Since = new[] { "refs/heads/br2", "refs/heads/master" } }, + repo => new CommitFilter { IncludeReachableFrom = new[] { "refs/heads/br2", "refs/heads/master" } }, new[] { "4c062a6", "a4a7dce", "be3563a", "c47800c", @@ -290,9 +329,12 @@ public void CanEnumerateCommitsFromTwoHeads() public void CanEnumerateCommitsFromMixedStartingPoints() { AssertEnumerationOfCommits( - repo => new Filter { 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", @@ -305,7 +347,7 @@ public void CanEnumerateCommitsFromMixedStartingPoints() public void CanEnumerateCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new Filter { 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" @@ -316,7 +358,7 @@ public void CanEnumerateCommitsUsingGlob() public void CanHideCommitsUsingGlob() { AssertEnumerationOfCommits( - repo => new Filter { 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" @@ -335,10 +377,10 @@ public void CanEnumerateCommitsFromATagAnnotation() CanEnumerateCommitsFromATag(t => t.Annotation); } - private static void CanEnumerateCommitsFromATag(Func transformer) + private void CanEnumerateCommitsFromATag(Func transformer) { AssertEnumerationOfCommits( - repo => new Filter { Since = transformer(repo.Tags["test"]) }, + repo => new CommitFilter { IncludeReachableFrom = transformer(repo.Tags["test"]) }, new[] { "e90810b", "6dcf9bf", } ); } @@ -347,7 +389,10 @@ private static void CanEnumerateCommitsFromATag(Func transformer) public void CanEnumerateAllCommits() { AssertEnumerationOfCommits( - repo => new Filter { Since = repo.Refs }, + repo => new CommitFilter + { + IncludeReachableFrom = repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal), + }, new[] { "44d5d18", "bb65291", "532740a", "503a16f", "3dfd6fd", @@ -361,36 +406,36 @@ public void CanEnumerateAllCommits() public void CanEnumerateCommitsFromATagWhichPointsToABlob() { AssertEnumerationOfCommits( - repo => new Filter { Since = repo.Tags["point_to_blob"] }, - new string[] { }); + repo => new CommitFilter { IncludeReachableFrom = repo.Tags["point_to_blob"] }, + Array.Empty()); } [Fact] public void CanEnumerateCommitsFromATagWhichPointsToATree() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(BareTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { string headTreeSha = repo.Head.Tip.Tree.Sha; Tag tag = repo.ApplyTag("point_to_tree", headTreeSha); AssertEnumerationOfCommitsInRepo(repo, - r => new Filter { 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); } } - private static void AssertEnumerationOfCommitsInRepo(Repository repo, Func filterBuilder, IEnumerable abbrevIds) + private static void AssertEnumerationOfCommitsInRepo(IRepository repo, Func filterBuilder, IEnumerable abbrevIds) { ICommitLog commits = repo.Commits.QueryBy(filterBuilder(repo)); @@ -402,7 +447,8 @@ private static void AssertEnumerationOfCommitsInRepo(Repository repo, Func(sha); Assert.Equal("testing\n", commit.Message); @@ -414,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); @@ -429,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()); @@ -455,21 +503,23 @@ 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"); var blob = commit["1/branch_file.txt"].Target as Blob; Assert.NotNull(blob); - Assert.Equal("hi\n", blob.ContentAsUtf8()); + Assert.Equal("hi\n", blob.GetContentText()); } } [Fact] public void CanDirectlyAccessATreeOfTheCommit() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4c062a6"); @@ -481,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"); @@ -489,98 +540,95 @@ public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() } } - [SkippableFact] + [Fact] public void CanCommitWithSignatureFromConfig() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + 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)); - InconclusiveIf(() => !repo.Config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); - const string relativeFilepath = "new.txt"; - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); + string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); + Commands.Stage(repo, relativeFilepath); - File.WriteAllText(filePath, "null"); - repo.Index.Stage(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"); - var name = repo.Config.Get("user.name"); - var email = repo.Config.Get("user.email"); - Assert.Equal(commit.Author.Name, name.Value); - Assert.Equal(commit.Author.Email, email.Value); - Assert.Equal(commit.Committer.Name, name.Value); - Assert.Equal(commit.Committer.Email, email.Value); + AssertCommitIdentitiesAre(commit, Constants.Identity); } } [Fact] public void CommitParentsAreMergeHeads() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Reset(ResetOptions.Hard, "c47800"); + repo.Reset(ResetMode.Hard, "c47800"); CreateAndStageANewFile(repo); - string mergeHeadPath = Path.Combine(repo.Info.Path, "MERGE_HEAD"); - File.WriteAllText(mergeHeadPath, "9fd738e8f7967c078dceed8190330fc8648ee56a\n"); + Touch(repo.Info.Path, "MERGE_HEAD", "9fd738e8f7967c078dceed8190330fc8648ee56a\n"); + + Assert.Equal(CurrentOperation.Merge, repo.Info.CurrentOperation); - Commit newMergedCommit = repo.Commit("Merge commit", DummySignature, DummySignature, false); + Commit newMergedCommit = repo.Commit("Merge commit", Constants.Signature, Constants.Signature); + + 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.Committer.Email); + Assert.NotNull(reflogEntry.Committer.Name); + Assert.Equal(string.Format("commit (merge): {0}", newMergedCommit.MessageShort), reflogEntry.Message); } } [Fact] public void CommitCleansUpMergeMetadata() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); Assert.True(Directory.Exists(dir)); const string relativeFilepath = "new.txt"; - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); - - File.WriteAllText(filePath, "this is a new file"); - repo.Index.Stage(relativeFilepath); + Touch(repo.Info.WorkingDirectory, relativeFilepath, "this is a new file"); + Commands.Stage(repo, relativeFilepath); - string mergeHeadPath = Path.Combine(repo.Info.Path, "MERGE_HEAD"); - string mergeMsgPath = Path.Combine(repo.Info.Path, "MERGE_MSG"); - string mergeModePath = Path.Combine(repo.Info.Path, "MERGE_MODE"); - string origHeadPath = Path.Combine(repo.Info.Path, "ORIG_HEAD"); - - File.WriteAllText(mergeHeadPath, "abcdefabcdefabcdefabcdefabcdefabcdefabcd"); - File.WriteAllText(mergeMsgPath, "This is a dummy merge.\n"); - File.WriteAllText(mergeModePath, "no-ff"); - File.WriteAllText(origHeadPath, "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef"); + string mergeHeadPath = Touch(repo.Info.Path, "MERGE_HEAD", "abcdefabcdefabcdefabcdefabcdefabcdefabcd"); + string mergeMsgPath = Touch(repo.Info.Path, "MERGE_MSG", "This is a dummy merge.\n"); + string mergeModePath = Touch(repo.Info.Path, "MERGE_MODE", "no-ff"); + string origHeadPath = Touch(repo.Info.Path, "ORIG_HEAD", "beefbeefbeefbeefbeefbeefbeefbeefbeefbeef"); Assert.True(File.Exists(mergeHeadPath)); Assert.True(File.Exists(mergeMsgPath)); Assert.True(File.Exists(mergeModePath)); Assert.True(File.Exists(origHeadPath)); - var author = DummySignature; + var author = Constants.Signature; repo.Commit("Initial egotistic commit", author, author); Assert.False(File.Exists(mergeHeadPath)); @@ -593,35 +641,64 @@ public void CommitCleansUpMergeMetadata() [Fact] public void CanCommitALittleBit() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); + + var identity = Constants.Identity; - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = identity })) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); Assert.True(Directory.Exists(dir)); const string relativeFilepath = "new.txt"; - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); - - File.WriteAllText(filePath, "null"); - repo.Index.Stage(relativeFilepath); + string filePath = Touch(repo.Info.WorkingDirectory, relativeFilepath, "null"); + Commands.Stage(repo, relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Index.Stage(relativeFilepath); + Commands.Stage(repo, relativeFilepath); Assert.Null(repo.Head[relativeFilepath]); - var author = DummySignature; - Commit commit = repo.Commit("Initial egotistic commit", author, author); + var author = Constants.Signature; + + 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.False(repo.Info.IsHeadOrphaned); + Assert.Empty(commit.Parents); + Assert.False(repo.Info.IsHeadUnborn); + + // Assert a reflog entry is created on HEAD + Assert.Single(repo.Refs.Log("HEAD")); + var reflogEntry = repo.Refs.Log("HEAD").First(); + + 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.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); @@ -629,23 +706,27 @@ 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 + Assert.Equal(2, repo.Refs.Log("HEAD").Count()); + 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"); @@ -654,19 +735,17 @@ public void CanCommitALittleBit() private static void AssertBlobContent(TreeEntry entry, string expectedContent) { - Assert.Equal(GitObjectType.Blob, entry.Type); - Assert.Equal(expectedContent, ((Blob)(entry.Target)).ContentAsUtf8()); + Assert.Equal(TreeEntryTargetType.Blob, entry.TargetType); + Assert.Equal(expectedContent, ((Blob)(entry.Target)).GetContentText()); } - private static void CommitToANewRepository(string path) + private static void AddCommitToRepo(string path) { - using (Repository repo = Repository.Init(path)) + using (var repo = new Repository(path)) { const string relativeFilepath = "test.txt"; - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); - - File.WriteAllText(filePath, "test\n"); - repo.Index.Stage(relativeFilepath); + Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); + 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); @@ -676,18 +755,19 @@ private static void CommitToANewRepository(string path) [Fact] public void CanGeneratePredictableObjectShas() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - CommitToANewRepository(scd.DirectoryPath); + AddCommitToRepo(repoPath); - using (var repo = new Repository(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { Commit commit = repo.Commits.Single(); Assert.Equal("1fe3126578fc4eca68c193e4a3a0a14a0704624d", commit.Sha); Tree tree = commit.Tree; Assert.Equal("2b297e643c551e76cfa1f93810c50811382f9117", tree.Sha); - Blob blob = tree.Blobs.Single(); + GitObject blob = tree.Single().Target; + Assert.IsAssignableFrom(blob); Assert.Equal("9daeafb9864cf43055ae93beb0afd6c7d144bfa4", blob.Sha); } } @@ -695,22 +775,23 @@ public void CanGeneratePredictableObjectShas() [Fact] public void CanAmendARootCommit() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - CommitToANewRepository(scd.DirectoryPath); + AddCommitToRepo(repoPath); - using (var repo = new Repository(scd.DirectoryPath)) + 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!", DummySignature, DummySignature, true); + 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); } @@ -719,33 +800,41 @@ public void CanAmendARootCommit() [Fact] public void CanAmendACommitWithMoreThanOneParent() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { var mergedCommit = repo.Lookup("be3563a"); Assert.NotNull(mergedCommit); Assert.Equal(2, mergedCommit.Parents.Count()); - repo.Reset(ResetOptions.Soft, mergedCommit.Sha); + repo.Reset(ResetMode.Soft, mergedCommit.Sha); CreateAndStageANewFile(repo); + const string commitMessage = "I'm rewriting the history!"; + + var before = DateTimeOffset.Now.TruncateMilliseconds(); - Commit amendedCommit = repo.Commit("I'm rewriting the history!", DummySignature, DummySignature, true); + Commit amendedCommit = repo.Commit(commitMessage, Constants.Signature, Constants.Signature, + new CommitOptions { AmendPreviousCommit = true }); AssertCommitHasBeenAmended(repo, amendedCommit, mergedCommit); + + AssertRefLogEntry(repo, "HEAD", + string.Format("commit (amend): {0}", commitMessage), + mergedCommit.Id, + amendedCommit.Id, + Constants.Identity, before); } } - private static void CreateAndStageANewFile(Repository repo) + private static void CreateAndStageANewFile(IRepository repo) { - string relativeFilepath = string.Format("new-file-{0}.txt", Guid.NewGuid()); - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); - - File.WriteAllText(filePath, "brand new content\n"); - repo.Index.Stage(relativeFilepath); + string relativeFilepath = string.Format("new-file-{0}.txt", Path.GetRandomFileName()); + Touch(repo.Info.WorkingDirectory, relativeFilepath, "brand new content\n"); + Commands.Stage(repo, relativeFilepath); } - private static void AssertCommitHasBeenAmended(Repository repo, Commit amendedCommit, Commit originalCommit) + private static void AssertCommitHasBeenAmended(IRepository repo, Commit amendedCommit, Commit originalCommit) { Commit headCommit = repo.Head.Tip; Assert.Equal(amendedCommit, headCommit); @@ -757,38 +846,39 @@ private static void AssertCommitHasBeenAmended(Repository repo, Commit amendedCo [Fact] public void CanNotAmendAnEmptyRepository() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (Repository repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.Commit("I can not amend anything !:(", DummySignature, DummySignature, true)); + Assert.Throws(() => + repo.Commit("I can not amend anything !:(", Constants.Signature, Constants.Signature, new CommitOptions { AmendPreviousCommit = true })); } } [Fact] public void CanRetrieveChildrenOfASpecificCommit() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { const string parentSha = "5b5b025afb0b4c913b4c338a42934a3863bf3644"; - var filter = new Filter - { - /* Revwalk from all the refs (git log --all) ... */ - Since = repo.Refs, + var filter = new CommitFilter + { + /* 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" }; @@ -800,13 +890,13 @@ from p in c.Parents [Fact] public void CanCorrectlyDistinguishAuthorFromCommitter() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + 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); @@ -819,22 +909,269 @@ public void CanCorrectlyDistinguishAuthorFromCommitter() public void CanCommitOnOrphanedBranch() { string newBranchName = "refs/heads/newBranch"; - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { // Set Head to point to branch other than master repo.Refs.UpdateTarget("HEAD", newBranchName); Assert.Equal(newBranchName, repo.Head.CanonicalName); const string relativeFilepath = "test.txt"; - string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); + Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); + Commands.Stage(repo, relativeFilepath); + + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + Assert.Single(repo.Head.Commits); + } + } + + [Fact] + public void CanNotCommitAnEmptyCommit() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Assert.Throws(() => repo.Commit("Empty commit!", Constants.Signature, Constants.Signature)); + } + } + + [Fact] + public void CanCommitAnEmptyCommitWhenForced() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + repo.Commit("Empty commit!", Constants.Signature, Constants.Signature, + new CommitOptions { AllowEmptyCommit = true }); + } + } + + [Fact] + public void CanNotAmendAnEmptyCommit() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + repo.Commit("Empty commit!", Constants.Signature, Constants.Signature, + new CommitOptions { AllowEmptyCommit = true }); + + Assert.Throws(() => repo.Commit("Empty commit!", Constants.Signature, Constants.Signature, + new CommitOptions { AmendPreviousCommit = true })); + } + } + + [Fact] + public void CanAmendAnEmptyCommitWhenForced() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Commit emptyCommit = repo.Commit("Empty commit!", Constants.Signature, Constants.Signature, + new CommitOptions { AllowEmptyCommit = true }); + + Commit amendedCommit = repo.Commit("I'm rewriting the history!", Constants.Signature, Constants.Signature, + new CommitOptions { AmendPreviousCommit = true, AllowEmptyCommit = true }); + AssertCommitHasBeenAmended(repo, amendedCommit, emptyCommit); + } + } + + [Fact] + public void CanCommitAnEmptyCommitWhenMerging() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Touch(repo.Info.Path, "MERGE_HEAD", "f705abffe7015f2beacf2abe7a36583ebee3487e\n"); + + Assert.Equal(CurrentOperation.Merge, repo.Info.CurrentOperation); + + Commit newMergedCommit = repo.Commit("Merge commit", Constants.Signature, Constants.Signature); + + Assert.Equal(2, newMergedCommit.Parents.Count()); + Assert.Equal("32eab9cb1f450b5fe7ab663462b77d7f4b703344", newMergedCommit.Parents.First().Sha); + Assert.Equal("f705abffe7015f2beacf2abe7a36583ebee3487e", newMergedCommit.Parents.Skip(1).First().Sha); + } + } + + [Fact] + public void CanAmendAnEmptyMergeCommit() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); + + Touch(repo.Info.Path, "MERGE_HEAD", "f705abffe7015f2beacf2abe7a36583ebee3487e\n"); + Commit newMergedCommit = repo.Commit("Merge commit", Constants.Signature, Constants.Signature); + + Commit amendedCommit = repo.Commit("I'm rewriting the history!", Constants.Signature, Constants.Signature, + new CommitOptions { AmendPreviousCommit = true }); + AssertCommitHasBeenAmended(repo, amendedCommit, newMergedCommit); + } + } + + [Fact] + public void CanNotAmendACommitInAWayThatWouldLeadTheNewCommitToBecomeEmpty() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + Touch(repo.Info.WorkingDirectory, "test.txt", "test\n"); + Commands.Stage(repo, "test.txt"); + + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + + Touch(repo.Info.WorkingDirectory, "new.txt", "content\n"); + Commands.Stage(repo, "new.txt"); + + repo.Commit("One commit", Constants.Signature, Constants.Signature); + + 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); - File.WriteAllText(filePath, "test\n"); - repo.Index.Stage(relativeFilepath); + Assert.Equal(signedId, signedId2); - repo.Commit("Initial commit", DummySignature, DummySignature); - Assert.Equal(1, repo.Head.Commits.Count()); + 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 d34291666..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; @@ -10,44 +11,15 @@ public class ConfigurationFixture : BaseFixture { private static void AssertValueInLocalConfigFile(string repoPath, string regex) { - var configFilePath = Path.Combine(repoPath, "config"); - 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(); + var configFilePath = Path.Combine(repoPath, ".git/config"); AssertValueInConfigFile(configFilePath, regex); } [Fact] public void CanUnsetAnEntryFromTheLocalConfiguration() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + 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,85 +49,217 @@ public void CanUnsetAnEntryFromTheGlobalConfiguration() } } - [SkippableFact] - public void CanGetGlobalStringValue() + [Fact] + public void CanAddAndReadMultivarFromTheLocalConfiguration() { - using (var repo = new Repository(StandardTestRepoPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - InconclusiveIf(() => !repo.Config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); + 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.NotNull(repo.Config.Get("user.name")); + Assert.Equal(new[] { "value1", "value2" }, repo.Config + .OfType>() + .Where(x => x.Key == "unittests.plugin" && x.Level == ConfigurationLevel.Local) + .Select(x => x.Value) + .ToArray()); } } - [SkippableFact] - public void CanGetGlobalStringValueWithoutRepo() + [Fact] + public void CanAddAndReadMultivarFromTheGlobalConfiguration() { - using (var config = new Configuration()) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - InconclusiveIf(() => !config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); + 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.NotNull(config.Get("user.name")); + 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.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")); + Assert.Equal(2, repo.Config.GetValueOrDefault("unittests.intsetting", ConfigurationLevel.Local)); + + Assert.Equal(0, repo.Config.GetValueOrDefault("missing.key")); + Assert.Equal(4, repo.Config.GetValueOrDefault("missing.key", 4)); + Assert.Equal(4, repo.Config.GetValueOrDefault("missing.key", () => 4)); } } [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")); + + Assert.Equal(0, repo.Config.GetValueOrDefault("missing.key")); + Assert.Equal(4, repo.Config.GetValueOrDefault("missing.key", 4)); + Assert.Equal(4, repo.Config.GetValueOrDefault("missing.key", () => 4)); } } [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); + + Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault("remote.origin.fetch")); + Assert.Equal("+refs/heads/*:refs/remotes/origin/*", repo.Config.GetValueOrDefault("remote.origin.fetch", ConfigurationLevel.Local)); + 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.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.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.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.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")); } } - [SkippableFact] + [Fact] public void CanEnumerateGlobalConfig() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - InconclusiveIf(() => !repo.Config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); - + CreateConfigurationWithDummyUser(repo, Constants.Identity); var entry = repo.Config.FirstOrDefault>(e => e.Key == "user.name"); Assert.NotNull(entry); - Assert.NotNull(entry.Value); + Assert.Equal(Constants.Signature.Name, entry.Value); } } [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); @@ -169,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"); @@ -179,69 +281,53 @@ public void CanEnumerateLocalConfigContainingAKeyWithNoValue() } [Fact] - public void CanSetBooleanValue() + public void CanFindInLocalConfig() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - repo.Config.Set("unittests.boolsetting", true); - - AssertValueInLocalConfigFile(path.RepositoryPath, "boolsetting = true$"); + var matches = repo.Config.Find("unit"); + + Assert.NotNull(matches); + Assert.Equal(new[] { "unittests.intsetting", "unittests.longsetting" }, + matches + .Select(m => m.Key) + .OrderBy(s => s) + .ToArray()); } } - [SkippableFact] - public void CanSetGlobalStringValue() + [Fact] + public void CanFindInGlobalConfig() { - using (var repo = new Repository(StandardTestRepoPath)) - { - InconclusiveIf(() => !repo.Config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); - var existing = repo.Config.Get("user.name"); - Assert.NotNull(existing); - - try - { - repo.Config.Set("user.name", "Unit Test", ConfigurationLevel.Global); + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var matches = repo.Config.Find("-rocks", ConfigurationLevel.Global); - AssertValueInGlobalConfigFile("name = Unit Test$"); - } - finally - { - repo.Config.Set("user.name", existing.Value, ConfigurationLevel.Global); - } + Assert.NotNull(matches); + Assert.Equal(new[] { "woot.this-rocks" }, + matches.Select(m => m.Key).ToArray()); } } - [SkippableFact] - public void CanSetGlobalStringValueWithoutRepo() + [Fact] + public void CanSetBooleanValue() { - using(var config = new Configuration()) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - InconclusiveIf(() => !config.HasConfig(ConfigurationLevel.Global), - "No Git global configuration available"); - - var existing = config.Get("user.name"); - Assert.NotNull(existing); - - try - { - config.Set("user.name", "Unit Test", ConfigurationLevel.Global); + repo.Config.Set("unittests.boolsetting", true); - AssertValueInGlobalConfigFile("name = Unit Test$"); - } - finally - { - config.Set("user.name", existing.Value, ConfigurationLevel.Global); - } + AssertValueInLocalConfigFile(path, "boolsetting = true$"); } } [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)); } @@ -250,55 +336,55 @@ public void SettingLocalConfigurationOutsideAReposThrows() [Fact] public void CanSetIntValue() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { repo.Config.Set("unittests.intsetting", 3); - AssertValueInLocalConfigFile(path.RepositoryPath, "intsetting = 3$"); + AssertValueInLocalConfigFile(path, "intsetting = 3$"); } } [Fact] public void CanSetLongValue() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { repo.Config.Set("unittests.longsetting", (long)451); - AssertValueInLocalConfigFile(path.RepositoryPath, "longsetting = 451"); + AssertValueInLocalConfigFile(path, "longsetting = 451"); } } [Fact] public void CanSetStringValue() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { repo.Config.Set("unittests.stringsetting", "val"); - AssertValueInLocalConfigFile(path.RepositoryPath, "stringsetting = val$"); + AssertValueInLocalConfigFile(path, "stringsetting = val$"); } } [Fact] public void CanSetAndReadUnicodeStringValue() { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { repo.Config.Set("unittests.stringsetting", "Juliën"); - AssertValueInLocalConfigFile(path.RepositoryPath, "stringsetting = Juliën$"); + AssertValueInLocalConfigFile(path, "stringsetting = Juliën$"); string val = repo.Config.Get("unittests.stringsetting").Value; Assert.Equal("Juliën", val); } // Make sure the change is permanent - using (var repo = new Repository(path.RepositoryPath)) + using (var repo = new Repository(path)) { string val = repo.Config.Get("unittests.stringsetting").Value; Assert.Equal("Juliën", val); @@ -308,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")); @@ -318,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")); @@ -330,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)); @@ -340,12 +429,8 @@ public void SettingUnsupportedTypeThrows() [Fact] public void CanGetAnEntryFromASpecificStore() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - var options = BuildFakeConfigs(scd); - - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath, options)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Assert.True(repo.Config.HasConfig(ConfigurationLevel.Local)); Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); @@ -365,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 d4db890b4..6317bf431 100644 --- a/LibGit2Sharp.Tests/ConflictFixture.cs +++ b/LibGit2Sharp.Tests/ConflictFixture.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -12,26 +13,112 @@ public static IEnumerable ConflictData { get { - return new[] + return new List { - new string[] { "ancestor-and-ours.txt", "5dee68477001f447f50fa7ee7e6a818370b5c2fb", "dad0664ae617d36e464ec08ed969ff496432b075", null }, - new string[] { "ancestor-and-theirs.txt", "3aafd4d0bac33cc3c78c4c070f3966fb6e6f641a", null, "7b26cd5ac0ee68483ae4d5e1e00b064547ea8c9b" }, - new string[] { "ancestor-only.txt", "9736f4cd77759672322f3222ed3ddead1412d969", null, null }, - new string[] { "conflicts-one.txt", "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81", "b7a41c703dc1f33185c76944177f3844ede2ee46", "516bd85f78061e09ccc714561d7b504672cb52da" }, - new string[] { "conflicts-two.txt", "84af62840be1b1c47b778a8a249f3ff45155038c", "ef70c7154145b09c7d08806e55fd0bfb7172576d", "220bd62631c8cf7a83ef39c6b94595f00517211e" }, - new string[] { "ours-and-theirs.txt", null, "9aaa9ae562a5f7362425a3fedc4d33ff74fe39e6", "0ca3f55d4ac2fa4703c149123b0b31d733112f86" }, - new string[] { "ours-only.txt", null, "9736f4cd77759672322f3222ed3ddead1412d969", null }, - new string[] { "theirs-only.txt", null, null, "9736f4cd77759672322f3222ed3ddead1412d969" }, + new[] { "ancestor-and-ours.txt", "5dee68477001f447f50fa7ee7e6a818370b5c2fb", "dad0664ae617d36e464ec08ed969ff496432b075", null }, + new[] { "ancestor-and-theirs.txt", "3aafd4d0bac33cc3c78c4c070f3966fb6e6f641a", null, "7b26cd5ac0ee68483ae4d5e1e00b064547ea8c9b" }, + new[] { "ancestor-only.txt", "9736f4cd77759672322f3222ed3ddead1412d969", null, null }, + new[] { "conflicts-one.txt", "1f85ca51b8e0aac893a621b61a9c2661d6aa6d81", "b7a41c703dc1f33185c76944177f3844ede2ee46", "516bd85f78061e09ccc714561d7b504672cb52da" }, + new[] { "conflicts-two.txt", "84af62840be1b1c47b778a8a249f3ff45155038c", "ef70c7154145b09c7d08806e55fd0bfb7172576d", "220bd62631c8cf7a83ef39c6b94595f00517211e" }, + new[] { "ours-and-theirs.txt", null, "9aaa9ae562a5f7362425a3fedc4d33ff74fe39e6", "0ca3f55d4ac2fa4703c149123b0b31d733112f86" }, + new[] { "ours-only.txt", null, "9736f4cd77759672322f3222ed3ddead1412d969", null }, + new[] { "theirs-only.txt", null, null, "9736f4cd77759672322f3222ed3ddead1412d969" }, }; } } - [Theory, PropertyData("ConflictData")] + private static List RenameConflictData + { + get + { + return new List + { + new[] { "3a-renamed-in-ours-deleted-in-theirs.txt", "3a-newname-in-ours-deleted-in-theirs.txt", null }, + new[] { "3b-renamed-in-theirs-deleted-in-ours.txt", null, "3b-newname-in-theirs-deleted-in-ours.txt" }, + new[] { "4a-renamed-in-ours-added-in-theirs.txt", "4a-newname-in-ours-added-in-theirs.txt", null }, + new[] { "4b-renamed-in-theirs-added-in-ours.txt", null, "4b-newname-in-theirs-added-in-ours.txt" }, + new[] { "5a-renamed-in-ours-added-in-theirs.txt", "5a-newname-in-ours-added-in-theirs.txt", "5a-renamed-in-ours-added-in-theirs.txt" }, + new[] { "5b-renamed-in-theirs-added-in-ours.txt", "5b-renamed-in-theirs-added-in-ours.txt", "5b-newname-in-theirs-added-in-ours.txt" }, + new[] { "6-both-renamed-1-to-2.txt", "6-both-renamed-1-to-2-ours.txt", "6-both-renamed-1-to-2-theirs.txt" }, + new[] { "7-both-renamed-side-1.txt", "7-both-renamed.txt", "7-both-renamed-side-1.txt" }, + new[] { "7-both-renamed-side-2.txt", "7-both-renamed-side-2.txt", "7-both-renamed.txt" }, + }; + } + } + + [Theory] + [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.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.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.NewInWorkdir, 1)] + public void CanResolveConflictsByRemovingFromTheIndex( + bool removeFromWorkdir, string filename, bool existsBeforeRemove, bool existsAfterRemove, FileStatus lastStatus, int removedIndexEntries) + { + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + + Assert.Equal(existsBeforeRemove, File.Exists(fullpath)); + Assert.NotNull(repo.Index.Conflicts[filename]); + Assert.Empty(repo.Index.Conflicts.ResolvedConflicts); + + 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.RetrieveStatus(filename)); + + Assert.Single(repo.Index.Conflicts.ResolvedConflicts); + Assert.NotNull(repo.Index.Conflicts.ResolvedConflicts[filename]); + } + } + + [Fact] + public void CanGetOriginalNamesOfRenameConflicts() + { + var path = Sandbox(MergeRenamesTestRepoWorkingDirPath); + using (var repo = new Repository(path)) + { + var expected = RenameConflictData; + var actual = repo.Index.Conflicts.Names; + + Assert.Equal(expected.Count, actual.Count()); + + int i = 0; + foreach (var name in actual) + { + Assert.Equal(expected[i][0], name.Ancestor); + Assert.Equal(expected[i][1], name.Ours); + Assert.Equal(expected[i][2], name.Theirs); + + i++; + } + } + } + + [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.Conflicts[filepath]; + Conflict conflict = repo.Index.Conflicts[filepath]; Assert.NotNull(conflict); ObjectId expectedAncestor = ancestorId != null ? new ObjectId(ancestorId) : null; @@ -76,9 +163,10 @@ 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.Conflicts.Select(c => new string[] { GetPath(c), GetId(c.Ancestor), GetId(c.Ours), GetId(c.Theirs) }).ToArray(); + 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 43d9e8ef8..8944bbeb4 100644 --- a/LibGit2Sharp.Tests/CurrentOperationFixture.cs +++ b/LibGit2Sharp.Tests/CurrentOperationFixture.cs @@ -1,5 +1,4 @@ using System.IO; -using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -13,8 +12,9 @@ public class CurrentOperationFixture : BaseFixture [InlineData(false)] public void CurrentOperationIsNoneForNewRepo(bool isBare) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath, isBare)) + string repoPath = InitNewRepository(isBare); + + using (var repo = new Repository(repoPath)) { Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); } @@ -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,27 +42,14 @@ public void CurrentOperationInNoneForABareRepo() [InlineData("rebase-merge/whatever", CurrentOperation.RebaseMerge)] public void CurrentOperationHasExpectedPendingOperationValues(string stateFile, CurrentOperation expectedState) { - var path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); + string path = SandboxStandardTestRepo(); - Touch(path.RepositoryPath, stateFile); + Touch(Path.Combine(path, ".git"), stateFile); - using (var repo = new Repository(path.RepositoryPath)) + using (var repo = new Repository(path)) { Assert.Equal(expectedState, repo.Info.CurrentOperation); } } - - private void Touch(string parent, string file) - { - var lastIndex = file.LastIndexOf('/'); - if (lastIndex > 0) - { - var parents = file.Substring(0, lastIndex); - Directory.CreateDirectory(Path.Combine(parent, parents)); - } - - var filePath = Path.Combine(parent, file); - File.AppendAllText(filePath, string.Empty, Encoding.ASCII); - } } } 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 1abc84354..046fe5214 100644 --- a/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs +++ b/LibGit2Sharp.Tests/DiffBlobToBlobFixture.cs @@ -11,9 +11,10 @@ public class DiffBlobToBlobFixture : BaseFixture [Fact] public void ComparingABlobAgainstItselfReturnsNoDifference() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Blob blob = repo.Head.Tip.Tree.Blobs.First(); + var blob = repo.Lookup("7909961"); ContentChanges changes = repo.Diff.Compare(blob, blob); @@ -26,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"); @@ -61,7 +63,7 @@ public void CanCompareTwoVersionsOfABlobWithADiffOfTwoHunks() } } - Blob CreateBinaryBlob(Repository repo) + Blob CreateBinaryBlob(IRepository repo) { string fullpath = Path.Combine(repo.Info.WorkingDirectory, "binary.bin"); @@ -73,13 +75,12 @@ Blob CreateBinaryBlob(Repository repo) [Fact] public void CanCompareATextualBlobAgainstABinaryBlob() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { Blob binBlob = CreateBinaryBlob(repo); - Blob blob = repo.Head.Tip.Tree.Blobs.First(); + var blob = repo.Lookup("7909961"); ContentChanges changes = repo.Diff.Compare(blob, binBlob); @@ -93,9 +94,10 @@ public void CanCompareATextualBlobAgainstABinaryBlob() [Fact] public void CanCompareABlobAgainstANullBlob() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Blob blob = repo.Head.Tip.Tree.Blobs.First(); + var blob = repo.Lookup("7909961"); ContentChanges changes = repo.Diff.Compare(null, blob); @@ -114,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); @@ -124,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 639d04e96..b712a214b 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTargetFixture.cs @@ -8,17 +8,16 @@ namespace LibGit2Sharp.Tests { public class DiffTreeToTargetFixture : BaseFixture { - private static void SetUpSimpleDiffContext(Repository repo) + private static void SetUpSimpleDiffContext(IRepository repo) { - var fullpath = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); - File.WriteAllText(fullpath, "hello\n"); + var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "hello\n"); - repo.Index.Stage(fullpath); - repo.Commit("Initial commit", DummySignature, DummySignature); + 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"); } @@ -38,49 +37,59 @@ private static void SetUpSimpleDiffContext(Repository repo) */ public void CanCompareASimpleTreeAgainstTheWorkDir() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges changes = 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(), changes.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; - TreeChanges 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)); + } } } @@ -98,26 +107,33 @@ public void CanCompareAMoreComplexTreeAgainstTheWorkdir() */ public void CanCompareASimpleTreeAgainstTheWorkDirAndTheIndex() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges changes = 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(), changes.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); + } } } @@ -145,47 +161,64 @@ public void CanCompareASimpleTreeAgainstTheWorkDirAndTheIndex() */ public void ShowcaseTheDifferenceBetweenTheTwoKindOfComparison() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); 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); - - - TreeChanges wrkDirToIdxToTree = 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(), wrkDirToIdxToTree.Patch); - - TreeChanges wrkDirToTree = 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(), wrkDirToTree.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); + } } } @@ -202,25 +235,32 @@ public void ShowcaseTheDifferenceBetweenTheTwoKindOfComparison() */ public void CanCompareASimpleTreeAgainstTheIndex() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges changes = 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(), changes.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); + } } } @@ -251,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; - TreeChanges 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); + } } } @@ -278,17 +321,61 @@ 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; - TreeChanges changes = repo.Diff.Compare(tree, DiffTargets.Index, - new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }); + using (var changes = repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt" })) + { + Assert.NotNull(changes); + + Assert.Single(changes); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + } + } + } + + private static void AssertCanCompareASubsetOfTheTreeAgainstTheIndex(TreeChanges changes) + { + Assert.NotNull(changes); + Assert.Single(changes); + Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + } - Assert.NotNull(changes); + [Fact] + public void CanCompareASubsetofTheTreeAgainstTheIndexWithLaxExplicitPathsValidationAndANonExistentPath() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Tree tree = repo.Head.Tip.Tree; - Assert.Equal(1, changes.Count()); - Assert.Equal("deleted_staged_file.txt", changes.Deleted.Single().Path); + 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() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Tree tree = repo.Head.Tip.Tree; + + Assert.Throws(() => repo.Diff.Compare(tree, DiffTargets.Index, + new[] { "deleted_staged_file.txt", "1/branch_file.txt", "I-do/not-exist" }, new ExplicitPathsOptions())); } } @@ -312,109 +399,134 @@ public void CanCompareASubsetofTheTreeAgainstTheIndex() */ public void CanCopeWithEndOfFileNewlineChanges() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { - var fullpath = Path.Combine(repo.Info.WorkingDirectory, "file.txt"); - File.WriteAllText(fullpath, "a"); + var fullpath = Touch(repo.Info.WorkingDirectory, "file.txt", "a"); - repo.Index.Stage("file.txt"); - repo.Commit("Add file without line ending", DummySignature, DummySignature); + 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"); - - TreeChanges changes = repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.LinesAdded); - Assert.Equal(1, changes.LinesDeleted); - - 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(), changes.Patch); + 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)); + () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.WorkingDirectory)); Assert.Throws( - () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)); + () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.Index)); Assert.Throws( - () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.WorkingDirectory | DiffTargets.Index)); + () => repo.Diff.Compare(repo.Head.Tip.Tree, DiffTargets.WorkingDirectory | DiffTargets.Index)); } } [Fact] public void CanCompareANullTreeAgainstTheIndex() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges 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(2, changes.Added.Single().LinesAdded); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } [Fact] public void CanCompareANullTreeAgainstTheWorkdir() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges 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(3, changes.Added.Single().LinesAdded); + Assert.Equal("file.txt", changes.Added.Single().Path); + } } } [Fact] public void CanCompareANullTreeAgainstTheWorkdirAndTheIndex() { - var scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + using (var repo = new Repository(repoPath)) { SetUpSimpleDiffContext(repo); - TreeChanges 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(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + Assert.Equal("file.txt", changes.Added.Single().Path); + } + } + } + + [Fact] + public void CompareSetsCorrectAddedAndDeletedLines() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + SetUpSimpleDiffContext(repo); - Assert.Equal("file.txt", changes.Added.Single().Path); - Assert.Equal(3, changes.Added.Single().LinesAdded); + 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 23157c5fb..8c2956331 100644 --- a/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs +++ b/LibGit2Sharp.Tests/DiffTreeToTreeFixture.cs @@ -9,32 +9,41 @@ 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; - TreeChanges changes = repo.Diff.Compare(tree, tree); + using (var changes = repo.Diff.Compare(tree, tree)) + { + Assert.Empty(changes); + } - Assert.Empty(changes); - Assert.Equal(string.Empty, changes.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; - TreeChanges 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")); + } } } @@ -46,26 +55,96 @@ 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; - TreeChanges changes = repo.Diff.Compare(parentCommitTree, commitTree); + using (var changes = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.Single(changes); + Assert.Single(changes.Added); + + TreeEntryChanges treeEntryChanges = changes.Single(c => c.Path == "1.txt"); + + Assert.Equal("1.txt", treeEntryChanges.Path); + Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); + + Assert.Equal(treeEntryChanges.Path, changes.Added.Single().Path); + + Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + } + + using (var patch = repo.Diff.Compare(parentCommitTree, commitTree)) + { + Assert.False(patch["1.txt"].IsBinaryComparison); + } + } + } + + static void CreateBinaryFile(string path) + { + var content = new byte[] { 0x1, 0x0, 0x2, 0x0 }; + + using (var binfile = File.Create(path)) + { + for (int i = 0; i < 1000; i++) + { + binfile.Write(content, 0, content.Length); + } + } + } + + [Fact] + public void CanDetectABinaryChange() + { + using (var repo = new Repository(SandboxStandardTestRepo())) + { + const string filename = "binfile.foo"; + var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); + + CreateBinaryFile(filepath); + + Commands.Stage(repo, filename); + var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); + + File.AppendAllText(filepath, "abcdef"); + + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + Assert.True(patch[filename].IsBinaryComparison); + + Commands.Stage(repo, filename); + var commit2 = repo.Commit("Update binary file", Constants.Signature, Constants.Signature); + + 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(SandboxStandardTestRepo())) + { + const string filename = "binfile.foo"; + var filepath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Added.Count()); + CreateBinaryFile(filepath); - TreeEntryChanges treeEntryChanges = changes["1.txt"]; - Assert.False(treeEntryChanges.IsBinaryComparison); + Commands.Stage(repo, filename); + var commit = repo.Commit("Add binary file", Constants.Signature, Constants.Signature); - Assert.Equal("1.txt", treeEntryChanges.Path); - Assert.Equal(ChangeKind.Added, treeEntryChanges.Status); + File.Delete(filepath); - Assert.Equal(treeEntryChanges, changes.Added.Single()); - Assert.Equal(1, treeEntryChanges.LinesAdded); + using (var patch = repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, new[] { filename })) + Assert.True(patch[filename].IsBinaryComparison); - Assert.Equal(Mode.Nonexistent, treeEntryChanges.OldMode); + Commands.Remove(repo, filename); + var commit2 = repo.Commit("Delete binary file", Constants.Signature, Constants.Signature); + + using (var patch2 = repo.Diff.Compare(commit.Tree, commit2.Tree, new[] { filename })) + Assert.True(patch2[filename].IsBinaryComparison); } } @@ -82,16 +161,19 @@ public void CanCompareACommitTreeAgainstItsParent() [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; - TreeChanges changes = repo.Diff.Compare(ancestor, tree, new[]{ "1", "2/" }); - 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); + } } } @@ -112,24 +194,67 @@ 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; - TreeChanges changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree); + 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()); + } + + 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() + { + 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; - 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, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } + + using (var changes = repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" })) + { + Assert.Empty(changes); + } + } + } - 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)); + [Fact] + public void ComparingATreeAgainstAnotherTreeWithStrictExplicitPathsValidationThrows() + { + 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; - Assert.Equal(9, changes.LinesAdded); - Assert.Equal(2, changes.LinesDeleted); - Assert.Equal(2, changes["readme.txt"].LinesDeleted); + Assert.Throws(() => + repo.Diff.Compare(commitTreeWithDifferentAncestor, commitTree, + new[] { "if-I-exist-this-test-is-really-unlucky.txt" }, new ExplicitPathsOptions())); } } @@ -151,23 +276,509 @@ public void CanCompareACommitTreeAgainstATreeWithNoCommonAncestor() * $ git diff -M --shortstat f8d44d7..4be51d6 * 1 file changed, 1 insertion(+) */ - [Fact(Skip = "Not implemented in libgit2 yet.")] - public void CanDetectTheRenamingOfAModifiedFile() + [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; - TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithRenamedFile); + 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); + } + } + } + + [Fact] + public void DetectsTheExactRenamingOfFilesByDefault() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + Commands.Move(repo, originalPath, renamedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + 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); + } + } + } + + [Fact(Skip = "Not supported by libgit2 as yet")] + public void RenameDetectionObeysConfigurationSetting() + { + // TODO: set the repo's diff.renames setting, and pass a structure that adjusts thresholds + } + + [Fact] + public void RenameThresholdsAreObeyed() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + + // 4 lines + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + 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"); + Commands.Stage(repo, originalPath); + Commands.Move(repo, originalPath, renamedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + var compareOptions = new CompareOptions + { + Similarity = new SimilarityOptions + { + RenameDetectionMode = RenameDetectionMode.Renames, + }, + }; + + compareOptions.Similarity.RenameThreshold = 30; + + 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; + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, compareOptions: compareOptions)) + Assert.DoesNotContain(changes, x => x.Status == ChangeKind.Renamed); + } + } + + [Fact] + public void ExactModeDetectsExactRenames() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + Commands.Move(repo, originalPath, renamedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: new CompareOptions + { + Similarity = SimilarityOptions.Exact, + })) + { + Assert.Single(changes); + Assert.Single(changes.Renamed); + Assert.Equal(originalPath, changes.Renamed.Single().OldPath); + Assert.Equal(renamedPath, changes.Renamed.Single().Path); + } + } + } + + [Fact] + public void ExactModeDetectsExactCopies() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + var originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + var copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + Commands.Stage(repo, originalPath); + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + File.Copy(originalFullPath, copiedFullPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: new CompareOptions + { + Similarity = SimilarityOptions.Exact, + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + } + } + } + + [Fact] + public void ExactModeDoesntDetectRenamesWithEdits() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + Commands.Move(repo, originalPath, renamedPath); + File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, renamedPath), "e\nf\n"); + Commands.Stage(repo, renamedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: new CompareOptions + { + Similarity = SimilarityOptions.Exact, + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + Assert.Single(changes.Added); + Assert.Single(changes.Deleted); + } + } + } + + [Fact] + public void CanIncludeUnmodifiedEntriesWhenDetectingTheExactRenamingOfFilesWhenEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + string originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + string copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + File.Copy(originalFullPath, copiedFullPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: + new CompareOptions + { + Similarity = SimilarityOptions.CopiesHarder, + IncludeUnmodified = true, + })) + { + 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); + } + } + } + + [Fact] + public void CanNotDetectTheExactRenamingFilesWhenNotEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + Commands.Move(repo, originalPath, renamedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: + new CompareOptions + { + Similarity = SimilarityOptions.None, + })) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Renamed); + } + } + } + + [Fact] + public void CanDetectTheExactCopyingOfNonModifiedFilesWhenEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + string originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + string copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + File.Copy(originalFullPath, copiedFullPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: + new CompareOptions + { + Similarity = SimilarityOptions.CopiesHarder, + })) + { + Assert.Single(changes); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } + } + } + + [Fact] + public void CanNotDetectTheExactCopyingOfNonModifiedFilesWhenNotEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + string originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + string copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); - 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.FilesRenamed.Count()); + File.Copy(originalFullPath, copiedFullPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Single(changes); + Assert.Empty(changes.Copied); + } } } + [Fact] + public void CanDetectTheExactCopyingOfModifiedFilesWhenEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + string originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + string copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + File.Copy(originalFullPath, copiedFullPath); + Touch(repo.Info.WorkingDirectory, originalPath, "e\n"); + + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: + new CompareOptions + { + Similarity = SimilarityOptions.Copies, + })) + { + Assert.Equal(2, changes.Count()); + Assert.Single(changes.Copied); + Assert.Equal(originalPath, changes.Copied.Single().OldPath); + Assert.Equal(copiedPath, changes.Copied.Single().Path); + } + } + } + + [Fact] + public void CanNotDetectTheExactCopyingOfModifiedFilesWhenNotEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string copiedPath = "copied.txt"; + string originalFullPath = Path.Combine(repo.Info.WorkingDirectory, originalPath); + string copiedFullPath = Path.Combine(repo.Info.WorkingDirectory, copiedPath); + + Touch(repo.Info.WorkingDirectory, originalPath, "a\nb\nc\nd\n"); + + Commands.Stage(repo, originalPath); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + File.Copy(originalFullPath, copiedFullPath); + File.AppendAllText(originalFullPath, "e\n"); + + Commands.Stage(repo, originalPath); + Commands.Stage(repo, copiedPath); + + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree)) + { + Assert.Equal(2, changes.Count()); + Assert.Empty(changes.Copied); + } + } + } + + [Fact] + public void CanIncludeUnmodifiedEntriesWhenEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + Touch(repo.Info.WorkingDirectory, "a.txt", "abc\ndef\n"); + Touch(repo.Info.WorkingDirectory, "b.txt", "abc\ndef\n"); + + 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"); + Commands.Stage(repo, "b.txt"); + Commit @new = repo.Commit("Updated", Constants.Signature, Constants.Signature); + + 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); + } + } + } + + [Fact] + public void CanDetectTheExactRenamingExactCopyingOfNonModifiedAndModifiedFilesWhenEnabled() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + var path = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(path)) + { + const string originalPath = "original.txt"; + const string renamedPath = "renamed.txt"; + const string originalPath2 = "original2.txt"; + const string copiedPath1 = "copied.txt"; + const string originalPath3 = "original3.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"); + + Commands.Stage(repo, originalPath); + Commands.Stage(repo, originalPath2); + Commands.Stage(repo, originalPath3); + + Commit old = repo.Commit("Initial", Constants.Signature, Constants.Signature); + + var originalFullPath2 = Path.Combine(repo.Info.WorkingDirectory, originalPath2); + var originalFullPath3 = Path.Combine(repo.Info.WorkingDirectory, originalPath3); + var copiedFullPath1 = Path.Combine(repo.Info.WorkingDirectory, copiedPath1); + var copiedFullPath2 = Path.Combine(repo.Info.WorkingDirectory, copiedPath2); + File.Copy(originalFullPath2, copiedFullPath1); + File.Copy(originalFullPath3, copiedFullPath2); + File.AppendAllText(originalFullPath3, "9\n"); + + 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); + + using (var changes = repo.Diff.Compare(old.Tree, @new.Tree, + compareOptions: + new CompareOptions + { + Similarity = SimilarityOptions.CopiesHarder, + })) + { + 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); + } + } + } /* * $ git diff f8d44d7..ec9e401 * diff --git a/numbers.txt b/numbers.txt @@ -190,23 +801,38 @@ public void CanDetectTheRenamingOfAModifiedFile() * $ git diff --shortstat f8d44d7..ec9e401 * 1 file changed, 2 insertions(+), 1 deletion(-) */ - [Fact] - public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion() + [Theory] + [InlineData(0, 175)] + [InlineData(1, 191)] + [InlineData(2, 184)] + [InlineData(3, 187)] + [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; - TreeChanges changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile); + using (var changes = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile)) + { + Assert.Single(changes); + Assert.Single(changes.Modified); + } - Assert.Equal(1, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); + using (var patch = repo.Diff.Compare(rootCommitTree, commitTreeWithUpdatedFile, + compareOptions: new CompareOptions { ContextLines = contextLines })) + { + Assert.Equal(expectedPatchLength, patch.Content.Length); - TreeEntryChanges treeEntryChanges = changes.Modified.Single(); + PatchEntryChanges entryChanges = patch["numbers.txt"]; - Assert.Equal(2, treeEntryChanges.LinesAdded); - Assert.Equal(1, treeEntryChanges.LinesDeleted); + Assert.Equal(2, entryChanges.LinesAdded); + Assert.Equal(1, entryChanges.LinesDeleted); + Assert.Equal(expectedPatchLength, entryChanges.Patch.Length); + Assert.Equal("numbers.txt", entryChanges.Path); + } } } @@ -261,112 +887,67 @@ public void CanCompareTwoVersionsOfAFileWithATrailingNewlineDeletion() * super-file.txt | 5 +++++ * 3 files changed, 8 insertions(+), 5 deletions(-) */ - [Fact] - public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks() + [Theory] + [InlineData(0, 3)] + [InlineData(0, 4)] + [InlineData(1, 1)] + [InlineData(1, 2)] + [InlineData(2, 4)] + [InlineData(2, 5)] + [InlineData(3, 2)] + [InlineData(3, 3)] + [InlineData(4, 0)] + [InlineData(4, 1)] + public void CanCompareTwoVersionsOfAFileWithADiffOfTwoHunks(int contextLines, int interhunkLines) { - using (var repo = new Repository(StandardTestRepoPath)) + var compareOptions = new CompareOptions + { + ContextLines = contextLines, + InterhunkLines = interhunkLines, + Similarity = SimilarityOptions.None, + }; + + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { Tree rootCommitTree = repo.Lookup("f8d44d7").Tree; Tree mergedCommitTree = repo.Lookup("7252fe2").Tree; - TreeChanges changes = repo.Diff.Compare(rootCommitTree, mergedCommitTree); - - Assert.Equal(3, changes.Count()); - Assert.Equal(1, changes.Modified.Count()); - Assert.Equal(1, changes.Deleted.Count()); - Assert.Equal(1, changes.Added.Count()); - - TreeEntryChanges treeEntryChanges = changes["numbers.txt"]; - - Assert.Equal(3, treeEntryChanges.LinesAdded); - Assert.Equal(1, treeEntryChanges.LinesDeleted); - - Assert.Equal(Mode.Nonexistent, changes["my-name-does-not-feel-right.txt"].Mode); - - var expected = new StringBuilder() - .Append("diff --git a/numbers.txt b/numbers.txt\n") - .Append("index 7909961..4e935b7 100644\n") - .Append("--- a/numbers.txt\n") - .Append("+++ b/numbers.txt\n") - .Append("@@ -1,4 +1,5 @@\n") - .Append(" 1\n") - .Append("+2\n") - .Append(" 3\n") - .Append(" 4\n") - .Append(" 5\n") - .Append("@@ -8,8 +9,9 @@\n") - .Append(" 8\n") - .Append(" 9\n") - .Append(" 10\n") - .Append("-12\n") - .Append("+11\n") - .Append(" 12\n") - .Append(" 13\n") - .Append(" 14\n") - .Append(" 15\n") - .Append("+16\n"); - - Assert.Equal(expected.ToString(), treeEntryChanges.Patch); - - expected = new StringBuilder() - .Append("diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt\n") - .Append("deleted file mode 100644\n") - .Append("index e8953ab..0000000\n") - .Append("--- a/my-name-does-not-feel-right.txt\n") - .Append("+++ /dev/null\n") - .Append("@@ -1,4 +0,0 @@\n") - .Append("-That's a terrible name!\n") - .Append("-I don't like it.\n") - .Append("-People look down at me and laugh. :-(\n") - .Append("-Really!!!!\n") - .Append("diff --git a/numbers.txt b/numbers.txt\n") - .Append("index 7909961..4e935b7 100644\n") - .Append("--- a/numbers.txt\n") - .Append("+++ b/numbers.txt\n") - .Append("@@ -1,4 +1,5 @@\n") - .Append(" 1\n") - .Append("+2\n") - .Append(" 3\n") - .Append(" 4\n") - .Append(" 5\n") - .Append("@@ -8,8 +9,9 @@\n") - .Append(" 8\n") - .Append(" 9\n") - .Append(" 10\n") - .Append("-12\n") - .Append("+11\n") - .Append(" 12\n") - .Append(" 13\n") - .Append(" 14\n") - .Append(" 15\n") - .Append("+16\n") - .Append("diff --git a/super-file.txt b/super-file.txt\n") - .Append("new file mode 100644\n") - .Append("index 0000000..16bdf1d\n") - .Append("--- /dev/null\n") - .Append("+++ b/super-file.txt\n") - .Append("@@ -0,0 +1,5 @@\n") - .Append("+That's a terrible name!\n") - .Append("+I don't like it.\n") - .Append("+People look down at me and laugh. :-(\n") - .Append("+Really!!!!\n") - .Append("+Yeah! Better!\n"); - - Assert.Equal(expected.ToString(), changes.Patch); - } - } - - [Fact] - public void CanHandleTwoTreeEntryChangesWithTheSamePath() + 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); + + Assert.Equal(Mode.Nonexistent, changes.Single(c => c.Path == "my-name-does-not-feel-right.txt").Mode); + } + + 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); + } + } + } + + private void CanHandleTwoTreeEntryChangesWithTheSamePath(SimilarityOptions similarity, Action verifier) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (Repository repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Blob mainContent = CreateBlob(repo, "awesome content\n"); - Blob linkContent = CreateBlob(repo, "../../objc/Nu.h"); + 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) @@ -379,97 +960,113 @@ public void CanHandleTwoTreeEntryChangesWithTheSamePath() Tree treeNew = repo.ObjectDatabase.CreateTree(tdNew); - TreeChanges changes = repo.Diff.Compare(treeOld, treeNew); - - /* - * $ 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); + using (var changes = repo.Diff.Compare(treeOld, treeNew, + compareOptions: new CompareOptions + { + Similarity = similarity, + })) + { + verifier(path, changes); + } } } - private static Blob CreateBlob(Repository repo, string content) + [Fact] + public void CanHandleTwoTreeEntryChangesWithTheSamePathUsingSimilarityNone() { - using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) - using (var binReader = new BinaryReader(stream)) - { - return repo.ObjectDatabase.CreateBlob(binReader); - } + // $ 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; - TreeChanges 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)) { - TreeChanges changes = repo.Diff.Compare(null, null, null); - - Assert.Equal(0, changes.Count()); + using (var changes = repo.Diff.Compare(default(Tree), default(Tree))) + { + Assert.Empty(changes); + } } } [Fact] public void ComparingReliesOnProvidedConfigEntriesIfAny() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - const string file = "1/branch_file.txt"; - using (var repo = new Repository(path.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { TreeEntry entry = repo.Head[file]; Assert.Equal(Mode.ExecutableFile, entry.Mode); @@ -477,55 +1074,222 @@ public void ComparingReliesOnProvidedConfigEntriesIfAny() // Recreate the file in the workdir without the executable bit string fullpath = Path.Combine(repo.Info.WorkingDirectory, file); File.Delete(fullpath); - File.WriteAllBytes(fullpath, ((Blob)(entry.Target)).Content); + using (var stream = ((Blob)(entry.Target)).GetContentStream()) + { + Touch(repo.Info.WorkingDirectory, file, stream); + } // Unset the local core.filemode, if any. - repo.Config.Unset("core.filemode", ConfigurationLevel.Local); + 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 change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } + } + + using (var repo = new Repository(path)) + { + SetFilemode(repo, false); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } + } + } + + void SetFilemode(Repository repo, bool value) + { + repo.Config.Set("core.filemode", value); + } + + [Fact] + public void RetrievingDiffChangesMustAlwaysBeCaseSensitive() + { + ObjectId treeOldOid, treeNewOid; - var options = BuildFakeSystemConfigFilemodeOption(scd, true); + string repoPath = InitNewRepository(); - using (var repo = new Repository(path.DirectoryPath, options)) + using (var repo = new Repository(repoPath)) { - TreeChanges 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(1, changes.Count()); + var td = new TreeDefinition() + .Add("A.TXT", oldContent, Mode.NonExecutableFile) + .Add("a.txt", oldContent, Mode.NonExecutableFile); - var change = changes.Modified.Single(); - Assert.Equal(Mode.ExecutableFile, change.OldMode); - Assert.Equal(Mode.NonExecutableFile, change.Mode); + treeOldOid = repo.ObjectDatabase.CreateTree(td).Id; + + td = new TreeDefinition() + .Add("A.TXT", newContent, Mode.NonExecutableFile) + .Add("a.txt", newContent, Mode.NonExecutableFile); + + treeNewOid = repo.ObjectDatabase.CreateTree(td).Id; + } + + 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); + } } + } - options = BuildFakeSystemConfigFilemodeOption(scd, false); + [Fact] + public void RetrievingDiffContainsRightAmountOfAddedAndDeletedLines() + { + ObjectId treeOldOid, treeNewOid; - using (var repo = new Repository(path.DirectoryPath, options)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { - TreeChanges changes = repo.Diff.Compare(new[] { file }); + Blob oldContent = OdbHelper.CreateBlob(repo, "awesome content\n"); + Blob newContent = OdbHelper.CreateBlob(repo, "more awesome content\n"); + + var td = new TreeDefinition() + .Add("A.TXT", oldContent, Mode.NonExecutableFile) + .Add("a.txt", oldContent, Mode.NonExecutableFile); + + treeOldOid = repo.ObjectDatabase.CreateTree(td).Id; + + td = new TreeDefinition() + .Add("A.TXT", newContent, Mode.NonExecutableFile) + .Add("a.txt", newContent, Mode.NonExecutableFile); - Assert.Equal(0, changes.Count()); + treeNewOid = repo.ObjectDatabase.CreateTree(td).Id; + } + + using (var repo = new Repository(repoPath)) + { + 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); + } + } } } - private RepositoryOptions BuildFakeSystemConfigFilemodeOption( - SelfCleaningDirectory scd, - bool value) + [Fact] + public void UsingPatienceAlgorithmCompareOptionProducesPatienceDiff() { - Directory.CreateDirectory(scd.DirectoryPath); + 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); + } + } - var options = new RepositoryOptions - { - SystemConfigurationLocation = Path.Combine( - scd.RootedDirectoryPath, "fake-system.config") - }; + [Fact] + public void DiffThrowsANotFoundExceptionIfATreeIsMissing() + { + string repoPath = SandboxBareTestRepo(); - StringBuilder sb = new StringBuilder() - .AppendFormat("[core]{0}", Environment.NewLine) - .AppendFormat("filemode = {1}{0}", Environment.NewLine, value); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); + // Manually delete the tree object to simulate a partial clone + File.Delete(Path.Combine(repoPath, "objects", "58", "1f9824ecaf824221bd36edf5430f2739a7c4f5")); - return options; + using (var repo = new Repository(repoPath)) + { + // 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 0419a0823..c6ef700bb 100644 --- a/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs +++ b/LibGit2Sharp.Tests/DiffWorkdirToIndexFixture.cs @@ -1,6 +1,10 @@ +using System; +using System.IO; using System.Linq; +using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -26,13 +30,151 @@ public class DiffWorkdirToIndexFixture : BaseFixture [Fact] public void CanCompareTheWorkDirAgainstTheIndex() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - TreeChanges changes = repo.Diff.Compare(); + 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.NewInWorkdir)] + [InlineData("really-i-cant-exist.txt", FileStatus.Nonexistent)] + public void CanCompareTheWorkDirAgainstTheIndexWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus currentStatus) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false })) + { + Assert.Empty(changes); + } + + using (var changes = repo.Diff.Compare(new[] { relativePath })) + { + Assert.Empty(changes); + } + } + } + + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] + [InlineData("really-i-cant-exist.txt", FileStatus.Nonexistent)] + public void ComparingTheWorkDirAgainstTheIndexWithStrictUnmatchedExplicitPathsValidationAndANonExistentPathspecThrows(string relativePath, FileStatus currentStatus) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Assert.Throws(() => repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions())); + } + } + + [Theory] + [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(); + + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + using (var changes = repo.Diff.Compare(new[] { relativePath }, false, new ExplicitPathsOptions + { + ShouldFailOnUnmatchedPath = false, + OnUnmatchedPath = callback.OnUnmatchedPath + })) + { + Assert.True(callback.WasCalled); + } + } + } + + private class AssertUnmatchedPathspecsCallbackIsCalled + { + public bool WasCalled; + + public void OnUnmatchedPath(string unmatchedpath) + { + WasCalled = true; + } + } + + [Fact] + public void ComparingReliesOnProvidedConfigEntriesIfAny() + { + const string file = "1/branch_file.txt"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + TreeEntry entry = repo.Head[file]; + Assert.Equal(Mode.ExecutableFile, entry.Mode); + + // Recreate the file in the workdir without the executable bit + string fullpath = Path.Combine(repo.Info.WorkingDirectory, file); + File.Delete(fullpath); + using (var stream = ((Blob)(entry.Target)).GetContentStream()) + { + Touch(repo.Info.WorkingDirectory, file, stream); + } + + // Unset the local core.filemode, if any. + repo.Config.Unset("core.filemode", ConfigurationLevel.Local); + } + + using (var repo = new Repository(path)) + { + SetFilemode(repo, true); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Single(changes); - 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); + var change = changes.Modified.Single(); + Assert.Equal(Mode.ExecutableFile, change.OldMode); + Assert.Equal(Mode.NonExecutableFile, change.Mode); + } + } + + using (var repo = new Repository(path)) + { + SetFilemode(repo, false); + using (var changes = repo.Diff.Compare(new[] { file })) + { + Assert.Empty(changes); + } + } + } + + void SetFilemode(Repository repo, bool value) + { + repo.Config.Set("core.filemode", value); + } + + [Fact] + public void CanCompareTheWorkDirAgainstTheIndexWithUntrackedFiles() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(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/EqualityFixture.cs b/LibGit2Sharp.Tests/EqualityFixture.cs new file mode 100644 index 000000000..168d5fb99 --- /dev/null +++ b/LibGit2Sharp.Tests/EqualityFixture.cs @@ -0,0 +1,52 @@ +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class EqualityFixture : BaseFixture + { + [Fact] + public void EqualityHelperCanTestNullInEquals() + { + var one = new ObjectWithEquality(); + var two = new ObjectWithEquality(); + var three = new ObjectWithEquality(ObjectId.Zero); + var four = new ObjectWithEquality(ObjectId.Zero); + + Assert.True(one.Equals(one)); + Assert.True(two.Equals(two)); + Assert.True(three.Equals(four)); + Assert.True(four.Equals(three)); + Assert.False(one.Equals(three)); + Assert.False(three.Equals(one)); + } + + [Fact] + public void EqualityHelperCanTestNullInHashCode() + { + var one = new ObjectWithEquality(); + var two = new ObjectWithEquality(); + var three = new ObjectWithEquality(ObjectId.Zero); + var four = new ObjectWithEquality(ObjectId.Zero); + + Assert.Equal(one.GetHashCode(), two.GetHashCode()); + Assert.Equal(three.GetHashCode(), four.GetHashCode()); + Assert.NotEqual(one.GetHashCode(), three.GetHashCode()); + } + + private class ObjectWithEquality : GitObject + { + private readonly ObjectId id; + + public ObjectWithEquality(ObjectId id = null) + { + this.id = id; + } + + public override ObjectId Id + { + get { return id; } + } + } + } +} diff --git a/LibGit2Sharp.Tests/FetchFixture.cs b/LibGit2Sharp.Tests/FetchFixture.cs new file mode 100644 index 000000000..b36da7ccd --- /dev/null +++ b/LibGit2Sharp.Tests/FetchFixture.cs @@ -0,0 +1,293 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class FetchFixture : BaseFixture + { + private const string remoteName = "testRemote"; + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanFetchIntoAnEmptyRepository(string url) + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Add(remoteName, url); + + // Set up structures for the expected results + // and verifying the RemoteUpdateTips callback. + TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance; + var expectedFetchState = new ExpectedFetchState(remoteName); + + // Add expected branch objects + foreach (KeyValuePair kvp in expectedResults.BranchTips) + { + expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value); + } + + // Add the expected tags + string[] expectedTagNames = { "blob", "commit_tree", "annotated_tag" }; + foreach (string tagName in expectedTagNames) + { + TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName]; + expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo); + } + + // Perform the actual fetch + Commands.Fetch(repo, remoteName, Array.Empty(), new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler }, null); + + // Verify the expected + expectedFetchState.CheckUpdatedReferences(repo); + } + } + + [SkippableFact] + public void CanFetchIntoAnEmptyRepositoryWithCredentials() + { + InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), + "Populate Constants.PrivateRepo* to run this test"); + + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); + + // Perform the actual fetch + 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")] + public void CanFetchAllTagsIntoAnEmptyRepository(string url) + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + 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 + 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 + 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.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")] + public void CanFetchCustomRefSpecsIntoAnEmptyRepository(string url, string localBranchName, string remoteBranchName) + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Add(remoteName, url); + + string refSpec = string.Format("refs/heads/{2}:refs/remotes/{0}/{1}", remoteName, localBranchName, remoteBranchName); + + // Set up structures for the expected results + // and verifying the RemoteUpdateTips callback. + TestRemoteInfo remoteInfo = TestRemoteInfo.TestRemoteInstance; + 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 + 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.StartsWith("fetch ", reflogEntry.Message); + } + } + + [Theory] + [InlineData(TagFetchMode.All, 4)] + [InlineData(TagFetchMode.None, 0)] + [InlineData(TagFetchMode.Auto, 3)] + public void FetchRespectsConfiguredAutoTagSetting(TagFetchMode tagFetchMode, int expectedTagCount) + { + string url = "http://github.com/libgit2/TestGitRepository"; + + 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(remoteName, + r => r.TagFetchMode = tagFetchMode); + + // Perform the actual fetch. + 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/FetchHeadFixture.cs b/LibGit2Sharp.Tests/FetchHeadFixture.cs deleted file mode 100644 index c6cc8656b..000000000 --- a/LibGit2Sharp.Tests/FetchHeadFixture.cs +++ /dev/null @@ -1,134 +0,0 @@ -using LibGit2Sharp.Tests.TestHelpers; -using System; -using System.IO; -using System.Linq; -using Xunit; -using Xunit.Extensions; - -namespace LibGit2Sharp.Tests -{ - public class FetchHeadFixture : BaseFixture - { - [Fact] - public void FetchHeadIsEmptyByDefault() - { - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) - { - Assert.Equal(0, repo.Network.FetchHeads.Count()); - } - } - - private class FetchHeadExpected - { - public String Name { get; set; } - public String RemoteName { get; set; } - public String Url { get; set; } - public string TargetSha { get; set; } - public bool ForMerge { get; set; } - } - - [Theory] - [InlineData("git://github.com/libgit2/TestGitRepository.git")] - public void CanIterateFetchHead(string url) - { - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Clone(url, scd.RootedDirectoryPath)) - { - repo.Reset(ResetOptions.Hard, "HEAD~2"); - - // Create a file, stage it, and commit it. - String filename = "b.txt"; - String fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); - File.WriteAllText(fullPath, ""); - repo.Index.Stage(filename); - repo.Commit("comment", Constants.Signature, Constants.Signature); - - // Issue the fetch. - repo.Fetch("origin"); - - // Retrieve the fetch heads and verify the expected result. - var expected = new[] { - new FetchHeadExpected - { - Name = "FETCH_HEAD[0]", - RemoteName = "refs/heads/master", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "49322bb17d3acc9146f98c97d078513228bbf3c0", - ForMerge = true, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[1]", - RemoteName = "refs/heads/first-merge", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "0966a434eb1a025db6b71485ab63a3bfbea520b6", - ForMerge = false, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[2]", - RemoteName = "refs/heads/no-parent", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "42e4e7c5e507e113ebbb7801b16b52cf867b7ce1", - ForMerge = false, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[3]", - RemoteName = "refs/tags/annotated_tag", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "d96c4e80345534eccee5ac7b07fc7603b56124cb", - ForMerge = false, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[4]", - RemoteName = "refs/tags/blob", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "55a1a760df4b86a02094a904dfa511deb5655905", - ForMerge = false, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[5]", - RemoteName = "refs/tags/commit_tree", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "8f50ba15d49353813cc6e20298002c0d17b0a9ee", - ForMerge = false, - }, - new FetchHeadExpected - { - Name = "FETCH_HEAD[6]", - RemoteName = "refs/tags/nearly-dangling", - Url = "git://github.com/libgit2/TestGitRepository.git", - TargetSha = "6e0c7bdb9b4ed93212491ee778ca1c65047cab4e", - ForMerge = false, - }, - }; - - VerifyFetchHead(repo.Network.FetchHeads.ToArray(), expected); - } - } - - private static void VerifyFetchHead(FetchHead[] actual, FetchHeadExpected[] expected) - { - Assert.NotNull(actual); - Assert.NotNull(expected); - - Assert.Equal(actual.Count(), expected.Count()); - - int i = 0; - foreach (FetchHead fetchHead in actual) - { - Assert.Equal(fetchHead.CanonicalName, expected[i].Name); - Assert.Equal(fetchHead.RemoteCanonicalName, expected[i].RemoteName); - Assert.Equal(fetchHead.Url, expected[i].Url); - Assert.Equal(fetchHead.Target.Sha, expected[i].TargetSha); - Assert.Equal(fetchHead.ForMerge, expected[i].ForMerge); - - i++; - } - } - } -} 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 new file mode 100644 index 000000000..de4663a22 --- /dev/null +++ b/LibGit2Sharp.Tests/FilterBranchFixture.cs @@ -0,0 +1,868 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class FilterBranchFixture : BaseFixture + { + private readonly Repository repo; + private bool succeeding; + private Exception error; + + public FilterBranchFixture() + { + string path = SandboxBareTestRepo(); + repo = new Repository(path); + } + + public override void Dispose() + { + repo.Dispose(); + base.Dispose(); + } + + [Fact] + public void CanRewriteHistoryWithoutChangingCommitMetadata() + { + var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); + + // Noop header rewriter + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = CommitRewriteInfo.From, + }, commits); + + AssertSucceedingButNotError(); + + Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); + 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 { IncludeReachableFrom = repo.Refs }).ToArray(); + + // Noop tree rewriter + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitTreeRewriter = TreeDefinition.From, + }, commits); + + AssertSucceedingButNotError(); + + Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); + 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 { IncludeReachableFrom = repo.Refs }).ToArray(); + + Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = + () => + { + succeeding = true; + throw new Exception(); + }, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "Rewritten " + c.Message), + }, commits) + ); + + AssertSucceedingButNotError(); + + Assert.Equal(originalRefs, repo.Refs.ToList().OrderBy(r => r.CanonicalName)); + 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 { IncludeReachableFrom = repo.Refs }).ToArray(); + + var thrown = Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = + ex => { throw new Exception("From OnError", ex); }, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => { throw new Exception("From CommitHeaderRewriter"); }, + }, commits) + ); + + AssertSucceedingNotFired(); + Assert.Equal("From OnError", thrown.Message); + Assert.NotNull(thrown.InnerException); + 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 { IncludeReachableFrom = repo.Refs }).ToArray()); + } + + [Fact] + public void ErrorThrownInOnErrorTakesPrecedenceOverErrorDuringCommitTreeRewriter() + { + var originalRefs = repo.Refs.ToList().OrderBy(r => r.CanonicalName); + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); + + var thrown = Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = + ex => { throw new Exception("From OnError", ex); }, + OnSucceeding = OnSucceeding, + CommitTreeRewriter = + c => { throw new Exception("From CommitTreeRewriter"); }, + }, commits) + ); + + AssertSucceedingNotFired(); + Assert.Equal("From OnError", thrown.Message); + Assert.NotNull(thrown.InnerException); + 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 { IncludeReachableFrom = repo.Refs }).ToArray()); + } + + [Fact] + public void CanRewriteAuthorOfCommits() + { + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => + CommitRewriteInfo.From(c, author: new Signature("Ben Straub", "me@example.com", c.Author.When)), + }, commits); + + AssertSucceedingButNotError(); + + 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] + public void CanRewriteAuthorOfCommitsOnlyBeingPointedAtByTags() + { + var commit = repo.ObjectDatabase.CreateCommit( + Constants.Signature, Constants.Signature, "I'm a lonesome commit", + repo.Head.Tip.Tree, Enumerable.Empty(), false); + + repo.Tags.Add("so-lonely", commit); + + repo.Tags.Add("so-lonely-but-annotated", commit, Constants.Signature, + "Yeah, baby! I'm going to be rewritten as well"); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "Bam!"), + }, commit); + + AssertSucceedingButNotError(); + + var lightweightTag = repo.Tags["so-lonely"]; + Assert.Equal("Bam!", ((Commit)lightweightTag.Target).Message); + + var annotatedTag = repo.Tags["so-lonely-but-annotated"]; + Assert.Equal("Bam!", ((Commit)annotatedTag.Target).Message); + } + + [Fact] + public void CanRewriteTrees() + { + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitTreeRewriter = + c => TreeDefinition.From(c) + .Remove("README"), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + Assert.True(repo.Head.Commits.All(c => c["README"] == null)); + } + + [Fact] + public void CanRewriteTreesByInjectingTreeEntry() + { + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches }).ToArray(); + + var currentReadme = repo.Head["README"]; + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitTreeRewriter = + c => c["README"] == null + ? TreeDefinition.From(c) + : TreeDefinition.From(c) + .Add("README", currentReadme), + }, commits); + + AssertSucceedingButNotError(); + + Assert.Equal(Array.Empty(), + repo.Commits + .QueryBy(new CommitFilter { IncludeReachableFrom = repo.Branches }) + .Where(c => c["README"] != null + && c["README"].Target.Id != currentReadme.Target.Id) + .ToArray()); + } + + // git log --graph --oneline --name-status --decorate + // + // * 4c062a6 (HEAD, master) directory was added + // | A 1/branch_file.txt + // * be3563a Merge branch 'br2' + // |\ + // | * c47800c branch commit one + // | | A branch_file.txt + // * | 9fd738e a fourth commit + // | | M new.txt + // * | 4a202b3 (packed-test) a third commit + // |/ + // | M README + // * 5b5b025 another commit + // | A new.txt + // * 8496071 testing + // A README + [Theory] + + // * 6d96779 (HEAD, master) directory was added + // | A 1/branch_file.txt + // * ca7a3ed Merge branch 'br2' + // |\ + // | * da8d9d0 branch commit one + // | | A branch_file.txt + // * | 38f9fac a fourth commit + // |/ + // | M new.txt + // * ded26fd (packed-test) another commit + // A new.txt + [InlineData(new[] { "README" }, 5, "6d96779")] + + // * dfb164b (HEAD, master) directory was added + // | A 1/branch_file.txt + // * 8ab4a5f Merge branch 'br2' + // |\ + // | * 23dd639 branch commit one + // | | A branch_file.txt + // * | 5222c0f (packed-test) a third commit + // |/ + // | M README + // * 8496071 testing + // A README + [InlineData(new[] { "new.txt" }, 5, "dfb164b")] + + // * f9ee587 (HEAD, master) directory was added + // | A 1/branch_file.txt + // * b87858a branch commit one + // A branch_file.txt + // + // NB: packed-test is gone + [InlineData(new[] { "new.txt", "README" }, 2, "f9ee587")] + + // * 446fde5 (HEAD, master) directory was added + // | A 1/branch_file.txt + // * 9fd738e a fourth commit + // | M new.txt + // * 4a202b3 (packed-test) a third commit + // | M README + // * 5b5b025 another commit + // | A new.txt + // * 8496071 testing + // A README + [InlineData(new[] { "branch_file.txt" }, 5, "446fde5")] + + // * be3563a (HEAD, master) Merge branch 'br2' + // |\ + // | * c47800c branch commit one + // | | A branch_file.txt + // * | 9fd738e a fourth commit + // | | M new.txt + // * | 4a202b3 (packed-test) a third commit + // |/ + // | M README + // * 5b5b025 another commit + // | A new.txt + // * 8496071 testing + // A README + [InlineData(new[] { "1" }, 6, "be3563a")] + + // If all trees are empty, master should be an orphan + [InlineData(new[] { "1", "branch_file.txt", "new.txt", "README" }, 0, null)] + public void CanPruneEmptyCommits(string[] treeEntriesToRemove, int expectedCommitCount, string expectedHead) + { + Assert.Equal(7, repo.Head.Commits.Count()); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + PruneEmptyCommits = true, + CommitTreeRewriter = + c => TreeDefinition.From(c) + .Remove(treeEntriesToRemove), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + Assert.Equal(expectedCommitCount, repo.Head.Commits.Count()); + + if (expectedHead == null) + { + Assert.Null(repo.Head.Tip); + } + else + { + Assert.Equal(expectedHead, repo.Head.Tip.Id.Sha.Substring(0, expectedHead.Length)); + } + + foreach (var treeEntry in treeEntriesToRemove) + { + Assert.True(repo.Head.Commits.All(c => c[treeEntry] == null), "Did not expect a tree entry at " + treeEntry); + } + } + + // * 41bc8c6 (packed) + // | + // * 5001298 <----- rewrite this commit message + [Fact] + public void OnlyRewriteSelectedCommits() + { + var commit = repo.Branches["packed"].Tip; + var parent = commit.Parents.Single(); + + Assert.StartsWith("5001298", parent.Sha); + Assert.NotEqual(Constants.Signature, commit.Author); + Assert.NotEqual(Constants.Signature, parent.Author); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, author: Constants.Signature), + }, parent); + + AssertSucceedingButNotError(); + + commit = repo.Branches["packed"].Tip; + parent = commit.Parents.Single(); + + Assert.False(parent.Sha.StartsWith("5001298")); + Assert.NotEqual(Constants.Signature, commit.Author); + Assert.Equal(Constants.Signature, parent.Author); + } + + [Theory] + [InlineData("refs/rewritten")] + [InlineData("refs/rewritten/")] + public void CanCustomizeTheNamespaceOfBackedUpRefs(string backupRefsNamespace) + { + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: ""), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + Assert.Contains(repo.Refs, x => x.CanonicalName.StartsWith("refs/original")); + + Assert.DoesNotContain(repo.Refs, x => x.CanonicalName.StartsWith("refs/rewritten")); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + BackupRefsNamespace = backupRefsNamespace, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "abc"), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + Assert.Contains(repo.Refs, x => x.CanonicalName.StartsWith("refs/rewritten")); + } + + [Fact] + public void RefRewritingRollsBackOnFailure() + { + IList origRefs = repo.Refs.OrderBy(r => r.CanonicalName).ToArray(); + + const string backupNamespace = "refs/original/"; + + var ex = Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + BackupRefsNamespace = backupNamespace, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: ""), + TagNameRewriter = + (n, a, t) => + { + var newRef1 = + repo.Refs.FromGlob(backupNamespace + "*").FirstOrDefault(); + + if (newRef1 == null) + return n; + + // At least one of the refs have been rewritten + // Let's make sure it's been updated to a new target + var oldName1 = newRef1.CanonicalName.Replace(backupNamespace, "refs/"); + Assert.NotEqual(origRefs.Single(r => r.CanonicalName == oldName1), + newRef1); + + // Violently interrupt the process + throw new Exception("BREAK"); + }, + }, repo.Lookup("6dcf9bf7541ee10456529833502442f385010c3d")) + ); + + AssertErrorFired(ex); + AssertSucceedingNotFired(); + + // Ensure all the refs have been restored to their original targets + var newRefs = repo.Refs.OrderBy(r => r.CanonicalName).ToArray(); + Assert.Equal(newRefs, origRefs); + } + + // This test should rewrite br2, but not packed-test: + // * a4a7dce (br2) Merge branch 'master' into br2 + // |\ + // | * 9fd738e a fourth commit + // | * 4a202b3 (packed-test) a third commit + // * | c47800c branch commit one <----- rewrite this one + // |/ + // * 5b5b025 another commit + // * 8496071 testing + [Fact] + public void DoesNotRewriteRefsThatDontChange() + { + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "abc"), + }, repo.Lookup("c47800c")); + + AssertSucceedingButNotError(); + + Assert.Null(repo.Refs["refs/original/heads/packed-test"]); + Assert.NotNull(repo.Refs["refs/original/heads/br2"]); + + // Ensure br2 is still a merge commit + var parents = repo.Branches["br2"].Tip.Parents.ToList(); + Assert.Equal(2, parents.Count()); + Assert.Contains(parents, c => c.Sha.StartsWith("9fd738e")); + Assert.Equal("abc", parents.Single(c => !c.Sha.StartsWith("9fd738e")).Message); + } + + [Fact] + public void CanNotOverWriteBackedUpReferences() + { + Assert.Empty(repo.Refs.FromGlob("refs/original/*")); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "abc"), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + var originalRefs = repo.Refs.FromGlob("refs/original/*").OrderBy(r => r.CanonicalName).ToArray(); + Assert.NotEmpty(originalRefs); + + var ex = Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "def"), + }, repo.Head.Commits) + ); + + AssertErrorFired(ex); + AssertSucceedingNotFired(); + + Assert.Equal("abc", repo.Head.Tip.Message); + + var newOriginalRefs = repo.Refs.FromGlob("refs/original/*").OrderBy(r => r.CanonicalName).ToArray(); + Assert.Equal(originalRefs, newOriginalRefs); + + Assert.DoesNotContain(repo.Refs, x => x.CanonicalName.StartsWith("refs/original/original/")); + } + + [Fact] + public void CanNotOverWriteAnExistingReference() + { + var commits = repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs }).ToArray(); + + var ex = Assert.Throws( + () => + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + TagNameRewriter = + (n, a, t) => "test", + }, commits) + ); + + AssertErrorFired(ex); + AssertSucceedingNotFired(); + + Assert.Empty(repo.Refs.FromGlob("refs/original/*")); + } + + // Graft the orphan "test" branch to the tip of "packed" + // + // Before: + // * e90810b (test, lw, e90810b, test) + // | + // * 6dcf9bf + // <------------------ note: no connection + // * 41bc8c6 (packed) + // | + // * 5001298 + // + // ... and after: + // + // * f558880 (test, lw, e90810b, test) + // | + // * 0c25efa + // | <------------------ add this link + // * 41bc8c6 (packed) + // | + // * 5001298 + [Fact] + public void CanRewriteParents() + { + var commitToRewrite = repo.Lookup("6dcf9bf"); + var newParent = repo.Lookup("41bc8c6"); + bool hasBeenCalled = false; + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitParentsRewriter = + c => + { + Assert.False(hasBeenCalled); + Assert.Empty(c.Parents); + hasBeenCalled = true; + return new[] { newParent }; + }, + }, commitToRewrite); + + AssertSucceedingButNotError(); + + Assert.Contains(newParent, repo.Lookup("refs/heads/test~").Parents); + Assert.True(hasBeenCalled); + } + + // Graft the orphan "test" branch to the tip of "packed" + // + // Before: + // * e90810b (test, tag: lw, tag: e90810b, tag: test) + // | + // * 6dcf9bf + // <------------------ note: no connection + // * 41bc8c6 (packed) + // | + // * 5001298 + // + // ... and after: + // + // * f558880 (test, tag: lw, tag: e90810b, tag: test) + // | + // * 0c25efa + // | <------------------ add this link + // * 41bc8c6 (packed) + // | + // * 5001298 + [Fact] + public void CanRewriteParentWithRewrittenCommit() + { + var commitToRewrite = repo.Lookup("6dcf9bf"); + var newParent = repo.Branches["packed"].Tip; + + Assert.StartsWith("41bc8c6", newParent.Sha); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitParentsRewriter = + c => + c.Id != commitToRewrite.Id + ? c.Parents + : new[] { newParent } + }, commitToRewrite); + + AssertSucceedingButNotError(); + + // Assert "packed" hasn't been rewritten + 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.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.StartsWith("0c25efa", rewrittenTestCommitParent.Sha); + + // Assert grand parent of rewritten commit + var rewrittenTestCommitGrandParent = rewrittenTestCommitParent.Parents.Single(); + Assert.StartsWith("41bc8c6", rewrittenTestCommitGrandParent.Sha); + } + + [Fact] + public void WritesCorrectReflogMessagesForSimpleRewrites() + { + EnableRefLog(repo); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: ""), + }, repo.Head.Commits); + + AssertSucceedingButNotError(); + + Assert.Equal("filter-branch: rewrite", repo.Refs.Log(repo.Refs["refs/heads/master"]).First().Message); + Assert.Equal("filter-branch: backup", repo.Refs.Log(repo.Refs["refs/original/heads/master"]).First().Message); + } + + [Fact] + public void CanProvideNewNamesForTags() + { + GitObject lwTarget = repo.Tags["lw"].Target; + GitObject e908Target = repo.Tags["e90810b"].Target; + GitObject testTarget = repo.Tags["test"].Target; + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: ""), + TagNameRewriter = TagNameRewriter, + }, repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = repo.Refs["refs/heads/test"] })); + + AssertSucceedingButNotError(); + + Assert.NotEqual(lwTarget, repo.Tags["lw_new_e90810b"].Target); + Assert.NotEqual(e908Target, repo.Tags["e90810b_new_7b43849"].Target); + Assert.NotEqual(testTarget, repo.Tags["test_new_b25fa35"].Target); + } + + [Fact] + public void CanRewriteSymbolicRefsPointingToTags() + { + const string tagRefName = "refs/tags/test"; + + repo.Refs.Add("refs/tags/one_tracker", tagRefName); + repo.Refs.Add("refs/tags/another_tracker", tagRefName); + repo.Refs.Add("refs/attic/dusty_tracker", "refs/tags/another_tracker"); + + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, author: Constants.Signature), + TagNameRewriter = TagNameRewriter, + }, repo.Lookup("e90810b8df")); + + AssertSucceedingButNotError(); + + // Ensure the initial tags don't exist anymore... + Assert.Null(repo.Refs["refs/tags/one_tracker"]); + Assert.Null(repo.Refs["refs/tags/another_tracker"]); + + // ...and have been backed up. + Assert.Equal(tagRefName, repo.Refs["refs/original/tags/one_tracker"].TargetIdentifier); + Assert.Equal(tagRefName, repo.Refs["refs/original/tags/another_tracker"].TargetIdentifier); + + // Ensure the renamed symref tags points to the renamed target + const string renamedTarget = "refs/tags/test_new_b25fa35"; + Assert.Equal(renamedTarget, repo.Refs["refs/tags/one_tracker_new_test"].TargetIdentifier); + Assert.Equal(renamedTarget, repo.Refs["refs/tags/another_tracker_new_test"].TargetIdentifier); + + // Ensure that the non tag symref points to a renamed target... + Assert.Equal("refs/tags/another_tracker_new_test", repo.Refs["refs/attic/dusty_tracker"].TargetIdentifier); + + // ...and has been backed up as well. + Assert.Equal("refs/tags/another_tracker", repo.Refs["refs/original/attic/dusty_tracker"].TargetIdentifier); + } + + [Fact] + public void HandlesNameRewritingOfChainedTags() + { + // Add a lightweight tag (A) that points to tag annotation (B) that points to another tag annotation (C), + // which points to a commit + var theCommit = repo.Lookup("6dcf9bf"); + var annotationC = repo.ObjectDatabase.CreateTagAnnotation("annotationC", theCommit, Constants.Signature, ""); + var annotationB = repo.ObjectDatabase.CreateTagAnnotation("annotationB", annotationC, Constants.Signature, ""); + var tagA = repo.Tags.Add("lightweightA", annotationB); + + // Rewrite the commit, renaming the tag + repo.Refs.RewriteHistory(new RewriteHistoryOptions + { + OnError = OnError, + OnSucceeding = OnSucceeding, + BackupRefsNamespace = "refs/original/", + CommitHeaderRewriter = + c => CommitRewriteInfo.From(c, message: "Rewrote"), + TagNameRewriter = TagNameRewriter, + }, repo.Lookup("6dcf9bf")); + + AssertSucceedingButNotError(); + + // Verify the rewritten tag-annotation chain + var newTagA = repo.Tags["lightweightA_new_d53d92e"]; + Assert.NotNull(newTagA); + Assert.NotEqual(newTagA, tagA); + Assert.True(newTagA.IsAnnotated); + + var newAnnotationB = newTagA.Annotation; + Assert.NotNull(newAnnotationB); + Assert.NotEqual(newAnnotationB, annotationB); + Assert.Equal("annotationB_ann_237c1b0", newAnnotationB.Name); + + var newAnnotationC = newAnnotationB.Target as TagAnnotation; + Assert.NotNull(newAnnotationC); + Assert.NotEqual(newAnnotationC, annotationC); + Assert.Equal("annotationC_ann_6dcf9bf", newAnnotationC.Name); + + var newCommit = newAnnotationC.Target as Commit; + Assert.NotNull(newCommit); + Assert.NotEqual(newCommit, theCommit); + Assert.Equal("Rewrote", newCommit.Message); + + // Ensure the original tag doesn't exist anymore + Assert.Null(repo.Tags["lightweightA"]); + + // ...but it's been backed up + Reference backedUpTag = repo.Refs["refs/original/tags/lightweightA"]; + Assert.NotNull(backedUpTag); + 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/"; + var t = target == null + ? "" + : target.StartsWith(tagPrefix) + ? target.Substring(tagPrefix.Length) + : target.Substring(0, 7); + + return name + (isAnnotated ? "_ann_" : "_new_") + t; + } + + private Action OnSucceeding + { + get + { + succeeding = false; + return () => succeeding = true; + } + } + + private void AssertSucceedingButNotError() + { + Assert.True(succeeding); + Assert.Null(error); + } + + private void AssertSucceedingNotFired() + { + Assert.False(succeeding); + } + + private Action OnError + { + get + { + error = null; + return ex => error = ex; + } + } + + private void AssertErrorFired(Exception ex) + { + Assert.Equal(ex, error); + } + } +} 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 new file mode 100644 index 000000000..925efc3d0 --- /dev/null +++ b/LibGit2Sharp.Tests/GlobalSettingsFixture.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Reflection; +using System.Text.RegularExpressions; +using LibGit2Sharp.Core; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class GlobalSettingsFixture : BaseFixture + { + [Fact] + public void CanGetMinimumCompiledInFeatures() + { + BuiltInFeatures features = GlobalSettings.Version.Features; + + Assert.True(features.HasFlag(BuiltInFeatures.Threads)); + Assert.True(features.HasFlag(BuiltInFeatures.Https)); + } + + [Fact] + public void CanRetrieveValidVersionString() + { + // Version string format is: + // Major.Minor.Patch[-previewTag]+libgit2-{libgit2_abbrev_hash}.{LibGit2Sharp_hash} (arch - features) + // Example output: + // "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.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[-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; + + Assert.Throws(() => { GlobalSettings.NativeLibraryPath = "C:/Foo"; }); + } + + [SkippableTheory] + [InlineData("x86")] + [InlineData("x64")] + public void LoadFromSpecifiedPath(string architecture) + { + Skip.IfNot(Platform.IsRunningOnNetFramework(), ".NET Framework only test."); + + 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 + { + 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 08bcb6cfc..fc9d65bc2 100644 --- a/LibGit2Sharp.Tests/IgnoreFixture.cs +++ b/LibGit2Sharp.Tests/IgnoreFixture.cs @@ -3,7 +3,6 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,30 +11,30 @@ public class IgnoreFixture : BaseFixture [Fact] public void TemporaryRulesShouldApplyUntilCleared() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, "Foo.cs"), "Bar"); + Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar"); - Assert.True(repo.Index.RetrieveStatus().Untracked.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.Contains("Foo.cs")); + Assert.DoesNotContain("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); repo.Ignore.ResetAllTemporaryRules(); - Assert.True(repo.Index.RetrieveStatus().Untracked.Contains("Foo.cs")); + Assert.Contains("Foo.cs", repo.RetrieveStatus().Untracked.Select(s => s.FilePath)); } } [Fact] public void IsPathIgnoredShouldVerifyWhetherPathIsIgnored() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, "Foo.cs"), "Bar"); + Touch(repo.Info.WorkingDirectory, "Foo.cs", "Bar"); Assert.False(repo.Ignore.IsPathIgnored("Foo.cs")); @@ -52,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)); @@ -62,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)); } @@ -71,17 +72,47 @@ public void AddingATemporaryRuleWithBadParamsThrows() [Fact] public void CanCheckIfAPathIsIgnoredUsingThePreferedPlatformDirectorySeparatorChar() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - string ignorePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(ignorePath, "/NewFolder\n/NewFolder/NewFolder"); + 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 768963569..4c0d150f0 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -11,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", @@ -29,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); } @@ -38,16 +38,19 @@ 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).ToArray()); + Assert.Equal(expectedEntries, + repo.Index.Select(e => e.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); } } [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,566 +79,428 @@ 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]; }); } } - [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)] - public void CanStage(string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) + [Fact] + public void CanRenameAFile() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { - int count = repo.Index.Count; - Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(0, repo.Index.Count); - repo.Index.Stage(relativePath); + const string oldName = "polite.txt"; - Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); - Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); - Assert.Equal(expectedStatusOnceStaged, repo.Index.RetrieveStatus(relativePath)); - } - } + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); - [Fact] - public void CanStageTheUpdationOfAStagedFile() - { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - int count = repo.Index.Count; - const string filename = "new_tracked_file.txt"; - IndexEntry blob = repo.Index[filename]; + Touch(repo.Info.WorkingDirectory, oldName, "hello test file\n"); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(oldName)); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Commands.Stage(repo, oldName); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(oldName)); - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), "brand new content"); - Assert.Equal(FileStatus.Added | FileStatus.Modified, repo.Index.RetrieveStatus(filename)); + // Generated through + // $ echo "hello test file" | git hash-object --stdin + const string expectedHash = "88df547706c30fa19f02f43cb2396e8129acfd9b"; + Assert.Equal(expectedHash, repo.Index[oldName].Id.Sha); - repo.Index.Stage(filename); - IndexEntry newBlob = repo.Index[filename]; + Assert.Equal(1, repo.Index.Count); - Assert.Equal(count, repo.Index.Count); - Assert.NotEqual(newBlob.Id, blob.Id); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); - } - } + Signature who = Constants.Signature; + repo.Commit("Initial commit", who, who); - [Theory] - [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] - [InlineData("deleted_staged_file.txt", FileStatus.Removed)] - public void StagingAnUnknownFileThrows(string relativePath, FileStatus status) - { - using (var repo = new Repository(StandardTestRepoPath)) - { - Assert.Null(repo.Index[relativePath]); - Assert.Equal(status, repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(oldName)); - Assert.Throws(() => repo.Index.Stage(relativePath)); - } - } + const string newName = "being.frakking.polite.txt"; - [Fact] - public void CanStageTheRemovalOfAStagedFile() - { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - int count = repo.Index.Count; - const string filename = "new_tracked_file.txt"; - Assert.NotNull(repo.Index[filename]); + Commands.Move(repo, oldName, newName); + Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(newName)); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); + Assert.Equal(1, repo.Index.Count); + Assert.Equal(expectedHash, repo.Index[newName].Id.Sha); - File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); - Assert.Equal(FileStatus.Added | FileStatus.Missing, repo.Index.RetrieveStatus(filename)); + who = who.TimeShift(TimeSpan.FromMinutes(5)); + Commit commit = repo.Commit("Fix file name", who, who); - repo.Index.Stage(filename); - Assert.Null(repo.Index[filename]); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); + Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(newName)); - Assert.Equal(count - 1, repo.Index.Count); - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(filename)); + Assert.Equal(expectedHash, commit.Tree[newName].Target.Id.Sha); } } - [Fact] - public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOfHead() + [Theory] + [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) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - int count = repo.Index.Count; - - string filename = Path.Combine("1", "branch_file.txt"); - const string posixifiedFileName = "1/branch_file.txt"; - ObjectId blobId = repo.Index[posixifiedFileName].Id; - - string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); + Assert.Equal(destStatus, repo.RetrieveStatus(destPath)); - File.AppendAllText(fullpath, "Is there there anybody out there?"); - repo.Index.Stage(filename); + Commands.Move(repo, sourcePath, destPath); - Assert.Equal(count, repo.Index.Count); - Assert.NotEqual((blobId), repo.Index[posixifiedFileName].Id); - - repo.Index.Unstage(posixifiedFileName); - - Assert.Equal(count, repo.Index.Count); - Assert.Equal(blobId, repo.Index[posixifiedFileName].Id); + Assert.Equal(sourcePostStatus, repo.RetrieveStatus(sourcePath)); + Assert.Equal(destPostStatus, repo.RetrieveStatus(destPath)); } } - [Fact] - public void CanStageANewFileInAPersistentManner() + [Theory] + [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) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) - { - const string filename = "unit_test.txt"; - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(filename)); - Assert.Null(repo.Index[filename]); - - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), "some contents"); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(filename)); - Assert.Null(repo.Index[filename]); + var destPaths = new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" }; - repo.Index.Stage(filename); - Assert.NotNull(repo.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); - } - - using (var repo = new Repository(path.RepositoryPath)) - { - const string filename = "unit_test.txt"; - Assert.NotNull(repo.Index[filename]); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(filename)); - } + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } - [Fact] - public void CanStageANewFileWithAFullPath() + [Theory] + [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir)] + public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - int count = repo.Index.Count; + 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" }; - const string filename = "new_untracked_file.txt"; - string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.True(File.Exists(fullPath)); + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); + } - repo.Index.Stage(fullPath); + [Theory] + [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" }; - Assert.Equal(count + 1, repo.Index.Count); - Assert.NotNull(repo.Index[filename]); - } + InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } - [Fact] - public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorCharacters() + private void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + var repoPath = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(repoPath)) { - int count = repo.Index.Count; + Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); - DirectoryInfo di = Directory.CreateDirectory(Path.Combine(repo.Info.WorkingDirectory, "Project")); - string file = Path.Combine("Project", "a_file.txt"); - - File.WriteAllText(Path.Combine(di.FullName, "a_file.txt"), "With backward slash on Windows!"); - - repo.Index.Stage(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); + foreach (var destPath in destPaths) + { + string path = destPath; + Assert.Throws(() => Commands.Move(repo, sourcePath, path)); + } } } [Fact] - public void CanStageAndUnstageAnIgnoredFile() + public void PathsOfIndexEntriesAreExpressedInNativeFormat() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - string gitignorePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(gitignorePath, "*.ign" + Environment.NewLine); + // Build relative path + string relFilePath = Path.Combine("directory", "Testfile.txt").Replace('\\', '/'); - const string relativePath = "Champa.ign"; - string gitignoredFile = Path.Combine(repo.Info.WorkingDirectory, relativePath); - File.WriteAllText(gitignoredFile, "On stage!" + Environment.NewLine); + string repoPath = InitNewRepository(); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); + using (var repo = new Repository(repoPath)) + { + Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); - repo.Index.Stage(relativePath); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(relativePath)); + // Stage the file + Commands.Stage(repo, relFilePath); - repo.Index.Unstage(relativePath); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); - } - } + // Get the index + Index index = repo.Index; - [Fact] - public void StagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() - { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); + // Get the index entry + IndexEntry ie = index[relFilePath]; - const string filename = "unit_test.txt"; - string fullPath = Path.Combine(di.FullName, filename); - File.WriteAllText(fullPath, "some contents"); + // Make sure the IndexEntry has been found + Assert.NotNull(ie); - Assert.Throws(() => repo.Index.Stage(fullPath)); + // Make sure that the (native) relFilePath and ie.Path are equal + Assert.Equal(relFilePath, ie.Path); } } [Fact] - public void StageFileWithBadParamsThrows() + public void CanReadIndexEntryAttributes() { - 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.Equal(Mode.NonExecutableFile, repo.Index["README"].Mode); + Assert.Equal(Mode.ExecutableFile, repo.Index["1/branch_file.txt"].Mode); } } - [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("new_untracked_file.txt", FileStatus.Untracked, false, FileStatus.Untracked, false, 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("where-am-I.txt", FileStatus.Nonexistent, false, FileStatus.Nonexistent, false, 0)] - public void CanUnStage(string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) + [Fact] + public void StagingAFileWhenTheIndexIsLockedThrowsALockedFileException() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - int count = repo.Index.Count; - Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); - Assert.Equal(currentStatus, repo.Index.RetrieveStatus(relativePath)); + string repoPath = InitNewRepository(); - repo.Index.Unstage(relativePath); + using (var repo = new Repository(repoPath)) + { + Touch(repo.Info.Path, "index.lock"); - Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); - Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); - Assert.Equal(expectedStatusOnceStaged, repo.Index.RetrieveStatus(relativePath)); + Touch(repo.Info.WorkingDirectory, "newfile", "my my, this is gonna crash\n"); + Assert.Throws(() => Commands.Stage(repo, "newfile")); } } [Fact] - public void CanUnstageTheRemovalOfAFile() + public void CanCopeWithExternalChangesToTheIndex() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - int count = repo.Index.Count; + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + Touch(scd.DirectoryPath, "a.txt", "a\n"); + Touch(scd.DirectoryPath, "b.txt", "b\n"); - const string filename = "deleted_staged_file.txt"; + string path = Repository.Init(scd.DirectoryPath); - string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); - Assert.False(File.Exists(fullPath)); + using (var repoWrite = new Repository(path)) + using (var repoRead = new Repository(path)) + { + var writeStatus = repoWrite.RetrieveStatus(); + Assert.True(writeStatus.IsDirty); + Assert.Equal(0, repoWrite.Index.Count); + + var readStatus = repoRead.RetrieveStatus(); + Assert.True(readStatus.IsDirty); + Assert.Equal(0, repoRead.Index.Count); - Assert.Equal(FileStatus.Removed, repo.Index.RetrieveStatus(filename)); + Commands.Stage(repoWrite, "*"); + repoWrite.Commit("message", Constants.Signature, Constants.Signature); - repo.Index.Unstage(filename); - Assert.Equal(count + 1, repo.Index.Count); + writeStatus = repoWrite.RetrieveStatus(); + Assert.False(writeStatus.IsDirty); + Assert.Equal(2, repoWrite.Index.Count); - Assert.Equal(FileStatus.Missing, repo.Index.RetrieveStatus(filename)); + readStatus = repoRead.RetrieveStatus(); + Assert.False(readStatus.IsDirty); + Assert.Equal(2, repoRead.Index.Count); } } [Fact] - public void CanUnstageUntrackedFileAgainstAnOrphanedHead() + public void CanResetFullyMergedIndexFromTree() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string path = SandboxStandardTestRepo(); - using (var repo = Repository.Init(scd.DirectoryPath)) + const string testFile = "new_tracked_file.txt"; + + // It is sufficient to check just one of the stage area changes, such as the added file, + // to verify that the index has indeed been read from the tree. + using (var repo = new Repository(path)) { - string relativePath = "a.txt"; - string absolutePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); + const string headIndexTreeSha = "e5d221fc5da11a3169bf503d76497c81be3207b6"; + + Assert.True(repo.Index.IsFullyMerged); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(testFile)); - File.WriteAllText(absolutePath, "hello test file\n", Encoding.ASCII); - repo.Index.Stage(absolutePath); + var headIndexTree = repo.Lookup(headIndexTreeSha); + Assert.NotNull(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - repo.Index.Unstage(relativePath); - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.Equal(0, status.Staged.Count()); - Assert.Equal(1, status.Untracked.Count()); + Assert.True(index.IsFullyMerged); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } - } - [Fact] - public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() - { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + // Check that the index was persisted to disk. + using (var repo = new Repository(path)) { - DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); - - const string filename = "unit_test.txt"; - string fullPath = Path.Combine(di.FullName, filename); - File.WriteAllText(fullPath, "some contents"); - - Assert.Throws(() => repo.Index.Unstage(fullPath)); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] - public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirAgainstAnOrphanedHeadThrows() + public void CanResetIndexWithUnmergedEntriesFromTree() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - SelfCleaningDirectory scd2 = BuildSelfCleaningDirectory(); + string path = SandboxMergedTestRepo(); - using (var repo = Repository.Init(scd2.DirectoryPath)) + const string testFile = "one.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)) { - DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); + const string headIndexTreeSha = "1cb365141a52dfbb24933515820eb3045fbca12b"; + + Assert.False(repo.Index.IsFullyMerged); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(testFile)); - const string filename = "unit_test.txt"; - string fullPath = Path.Combine(di.FullName, filename); - File.WriteAllText(fullPath, "some contents"); + var headIndexTree = repo.Lookup(headIndexTreeSha); + Assert.NotNull(headIndexTree); + var index = repo.Index; + index.Replace(headIndexTree); + index.Write(); - Assert.Throws(() => repo.Index.Unstage(fullPath)); + Assert.True(index.IsFullyMerged); + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } - } - [Fact] - public void UnstagingFileWithBadParamsThrows() - { - using (var repo = new Repository(StandardTestRepoPath)) + // Check that the index was persisted to disk. + 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.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] - public void CanRenameAFile() + public void CanClearTheIndex() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string path = SandboxStandardTestRepo(); + const string testFile = "1.txt"; - using (var repo = Repository.Init(scd.DirectoryPath)) + // 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(0, repo.Index.Count); - - const string oldName = "polite.txt"; - string oldPath = Path.Combine(repo.Info.WorkingDirectory, oldName); - - Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus(oldName)); - - File.WriteAllText(oldPath, "hello test file\n", Encoding.ASCII); - Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus(oldName)); - - repo.Index.Stage(oldName); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus(oldName)); - - // Generated through - // $ echo "hello test file" | git hash-object --stdin - const string expectedHash = "88df547706c30fa19f02f43cb2396e8129acfd9b"; - Assert.Equal(expectedHash, repo.Index[oldName].Id.Sha); - - Assert.Equal(1, repo.Index.Count); - - Signature who = Constants.Signature; - repo.Commit("Initial commit", who, who); - - Assert.Equal(FileStatus.Unaltered, repo.Index.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)); - - Assert.Equal(1, repo.Index.Count); - Assert.Equal(expectedHash, repo.Index[newName].Id.Sha); - - 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.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)); + } - Assert.Equal(expectedHash, commit.Tree[newName].Target.Id.Sha); + // Check that the index was persisted to disk. + using (var repo = new Repository(path)) + { + Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } } [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)] - public void CanMoveAnExistingFileOverANonExistingFile(string sourcePath, FileStatus sourceStatus, string destPath, FileStatus destStatus, FileStatus sourcePostStatus, FileStatus destPostStatus) + [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) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(sourceStatus, repo.Index.RetrieveStatus(sourcePath)); - Assert.Equal(destStatus, repo.Index.RetrieveStatus(destPath)); + var before = repo.RetrieveStatus(pathInTheIndex); + Assert.Equal(expectedBeforeStatus, before); - repo.Index.Move(sourcePath, destPath); + repo.Index.Remove(pathInTheIndex); - Assert.Equal(sourcePostStatus, repo.Index.RetrieveStatus(sourcePath)); - Assert.Equal(destPostStatus, repo.Index.RetrieveStatus(destPath)); + var after = repo.RetrieveStatus(pathInTheIndex); + Assert.Equal(expectedAfterStatus, after); } } [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) - { - 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) - { - 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("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) { - InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); - } - - private static void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) - { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(sourceStatus, repo.Index.RetrieveStatus(sourcePath)); + var before = repo.RetrieveStatus(pathInTheWorkdir); + Assert.Equal(expectedBeforeStatus, before); - foreach (var destPath in destPaths) - { - string path = destPath; - Assert.Throws(() => repo.Index.Move(sourcePath, path)); - } + repo.Index.Add(pathInTheWorkdir); + + var after = repo.RetrieveStatus(pathInTheWorkdir); + Assert.Equal(expectedAfterStatus, after); } } - [Theory] - [InlineData("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Removed)] - [InlineData("deleted_unstaged_file.txt", FileStatus.Missing, false, FileStatus.Removed)] - public void CanRemoveAFile(string filename, FileStatus initialStatus, bool shouldInitiallyExist, FileStatus finalStatus) + [Fact] + public void CanAddAnEntryToTheIndexFromABlob() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - int count = repo.Index.Count; - - string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + const string targetIndexEntryPath = "1.txt"; + var before = repo.RetrieveStatus(targetIndexEntryPath); + Assert.Equal(FileStatus.Unaltered, before); - Assert.Equal(shouldInitiallyExist, File.Exists(fullpath)); - Assert.Equal(initialStatus, repo.Index.RetrieveStatus(filename)); + var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); - repo.Index.Remove(filename); + repo.Index.Add(blob, targetIndexEntryPath, Mode.NonExecutableFile); - Assert.Equal(count - 1, repo.Index.Count); - Assert.False(File.Exists(fullpath)); - Assert.Equal(finalStatus, repo.Index.RetrieveStatus(filename)); + var after = repo.RetrieveStatus(targetIndexEntryPath); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, after); } } - [Theory] - [InlineData("deleted_staged_file.txt")] - [InlineData("modified_unstaged_file.txt")] - [InlineData("shadowcopy_of_an_unseen_ghost.txt")] - public void RemovingAInvalidFileThrows(string filepath) + [Fact] + public void AddingAnEntryToTheIndexFromAUnknwonFileInTheWorkdirThrows() { - using (var repo = new Repository(StandardTestRepoPath)) + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Index.Remove(filepath)); + 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 RemovingFileWithBadParamsThrows() + public void CanMimicGitAddAll() { - 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 })); + 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 PathsOfIndexEntriesAreExpressedInNativeFormat() + public void RetrievingAssumedUnchangedMarkedIndexEntries() { - // Initialize a new repository - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - const string directoryName = "directory"; - const string fileName = "Testfile.txt"; - - // Create a file and insert some content - string directoryPath = Path.Combine(scd.RootedDirectoryPath, directoryName); - string filePath = Path.Combine(directoryPath, fileName); - - Directory.CreateDirectory(directoryPath); - File.WriteAllText(filePath, "Anybody out there?"); - - // Initialize the repository - using (var repo = Repository.Init(scd.DirectoryPath)) + var path = SandboxAssumeUnchangedTestRepo(); + using (var repo = new Repository(path)) { - // Stage the file - repo.Index.Stage(filePath); - - // Get the index - Index index = repo.Index; - - // Build relative path - string relFilePath = Path.Combine(directoryName, fileName); - - // Get the index entry - IndexEntry ie = index[relFilePath]; - - // Make sure the IndexEntry has been found - Assert.NotNull(ie); + var regularFile = repo.Index["hello.txt"]; + Assert.False(regularFile.AssumeUnchanged); - // Make sure that the (native) relFilePath and ie.Path are equal - Assert.Equal(relFilePath, ie.Path); + var assumeUnchangedFile = repo.Index["world.txt"]; + Assert.True(assumeUnchangedFile.AssumeUnchanged); } } - [Fact] - public void CanReadIndexEntryAttributes() + private static void AddSomeCornerCases(Repository repo) { - using (var repo = new Repository(StandardTestRepoPath)) - { - Assert.Equal(Mode.NonExecutableFile, repo.Index["README"].Mode); - Assert.Equal(Mode.ExecutableFile, repo.Index["1/branch_file.txt"].Mode); - } + // 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/LazyFixture.cs b/LibGit2Sharp.Tests/LazyFixture.cs deleted file mode 100644 index 14b797ea6..000000000 --- a/LibGit2Sharp.Tests/LazyFixture.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using LibGit2Sharp.Core.Compat; -using Xunit; - -namespace LibGit2Sharp.Tests -{ - public class LazyFixture - { - [Fact] - public void CanReturnTheValue() - { - var lazy = new Lazy(() => 2); - Assert.Equal(2, lazy.Value); - } - - [Fact] - public void IsLazilyEvaluated() - { - int i = 0; - - var evaluator = new Func(() => ++i); - - var lazy = new Lazy(evaluator); - Assert.Equal(1, lazy.Value); - } - - [Fact] - public void IsEvaluatedOnlyOnce() - { - int i = 0; - - var evaluator = new Func(() => ++i); - - var lazy = new Lazy(evaluator); - - Assert.Equal(1, lazy.Value); - Assert.Equal(1, lazy.Value); - } - } -} diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 2d54be3da..fb81a76a3 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -1,131 +1,43 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {286E63EB-04DD-4ADE-88D6-041B57800761} - Library - Properties - LibGit2Sharp.Tests - LibGit2Sharp.Tests - v3.5 - 512 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET35 - prompt - 4 - - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - true - full - false - bin\Leaks\ - TRACE;DEBUG;NET35;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 - + + + + - - - - - - - - - - - - + + + + <_TestAppFile Include="@(TestAppExe->'%(RootDir)%(Directory)%(Filename).exe')" /> + <_TestAppFile Include="@(TestAppExe->'%(RootDir)%(Directory)%(Filename).pdb')" /> + + + + + + + + \ 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 2117ba1cd..7b1fda718 100644 --- a/LibGit2Sharp.Tests/MergeFixture.cs +++ b/LibGit2Sharp.Tests/MergeFixture.cs @@ -1,7 +1,9 @@ -using System.IO; +using System; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -10,21 +12,21 @@ public class MergeFixture : BaseFixture [Fact] public void ANewRepoIsFullyMerged() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { - Assert.Equal(true, repo.Index.IsFullyMerged); - Assert.Empty(repo.MergeHeads); + 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.Empty(repo.MergeHeads); + Assert.True(repo.Index.IsFullyMerged); foreach (var entry in repo.Index) { @@ -36,25 +38,27 @@ 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(); Assert.Throws( - () => repo.Reset(ResetOptions.Soft, firstCommitParent)); + () => repo.Reset(ResetMode.Soft, firstCommitParent)); } } [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 = DummySignature; + var author = Constants.Signature; Assert.Throws( () => repo.Commit("Try commit unmerged entries", author, author)); } @@ -63,24 +67,898 @@ public void CommitAgainARepoWithUnmergedEntriesThrows() [Fact] public void CanRetrieveTheBranchBeingMerged() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { const string firstBranch = "9fd738e8f7967c078dceed8190330fc8648ee56a"; const string secondBranch = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"; - string mergeHeadPath = Path.Combine(repo.Info.Path, "MERGE_HEAD"); - File.WriteAllText(mergeHeadPath, - string.Format("{0}{1}{2}{1}", firstBranch, "\n", secondBranch)); + Touch(repo.Info.Path, "MERGE_HEAD", string.Format("{0}{1}{2}{1}", firstBranch, "\n", secondBranch)); Assert.Equal(CurrentOperation.Merge, repo.Info.CurrentOperation); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanMergeRepoNonFastForward(bool shouldMergeOccurInDetachedHeadState) + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + 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). + AddFileCommitToRepo(repo, sharedBranchFileName); + + var secondBranch = repo.CreateBranch("SecondBranch"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + if (shouldMergeOccurInDetachedHeadState) + { + // Detaches HEAD + Commands.Checkout(repo, secondBranch.Tip); + } + else + { + 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); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.NonFastForward, mergeResult.Status); - MergeHead[] mergedHeads = repo.MergeHeads.ToArray(); - Assert.Equal("MERGE_HEAD[0]", mergedHeads[0].Name); - Assert.Equal(firstBranch, mergedHeads[0].Tip.Id.Sha); - Assert.Equal("MERGE_HEAD[1]", mergedHeads[1].Name); - Assert.Null(mergedHeads[1].Tip); + Assert.Equal(repo.Head.Tip, mergeResult.Commit); + Assert.Equal(originalTreeCount + 3, mergeResult.Commit.Tree.Count); // Expecting original tree count plussed by the 3 added files. + Assert.Equal(2, mergeResult.Commit.Parents.Count()); // Merge commit should have 2 parents + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); + + if (!shouldMergeOccurInDetachedHeadState) + { + // Ensure HEAD is still attached and points to SecondBranch + Assert.Equal(repo.Refs.Head.TargetIdentifier, secondBranch.CanonicalName); + } } } + + [Fact] + public void IsUpToDateMerge() + { + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + 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"); + + Commands.Checkout(repo, secondBranch); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.UpToDate, mergeResult.Status); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanFastForwardRepos(bool shouldMergeOccurInDetachedHeadState) + { + const string firstBranchFileName = "first branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + // Reset the index and the working tree. + repo.Reset(ResetMode.Hard); + + // Clean the working directory. + repo.RemoveUntrackedFiles(); + + var firstBranch = repo.CreateBranch("FirstBranch"); + 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"); + + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + + if (shouldMergeOccurInDetachedHeadState) + { + // Detaches HEAD + Commands.Checkout(repo, secondBranch.Tip); + } + else + { + Commands.Checkout(repo, secondBranch); + } + + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.FastForward, mergeResult.Status); + Assert.Equal(repo.Branches["FirstBranch"].Tip, mergeResult.Commit); + Assert.Equal(repo.Branches["FirstBranch"].Tip, repo.Head.Tip); + Assert.Equal(repo.Head.Tip, mergeResult.Commit); + + Assert.Empty(repo.RetrieveStatus()); + Assert.Equal(shouldMergeOccurInDetachedHeadState, repo.Info.IsHeadDetached); + + if (!shouldMergeOccurInDetachedHeadState) + { + // Ensure HEAD is still attached and points to SecondBranch + Assert.Equal(repo.Refs.Head.TargetIdentifier, secondBranch.CanonicalName); + } + } + } + + [Fact] + public void ConflictingMergeRepos() + { + const string firstBranchFileName = "first branch file.txt"; + const string secondBranchFileName = "second branch file.txt"; + const string sharedBranchFileName = "first+second branch file.txt"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + 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"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "The first branches comment"); // Change file in first branch + + 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 + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Null(mergeResult.Commit); + 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)); + + Assert.False(changes.IsBinaryComparison); + } + } + + [Fact] + public void ConflictingMergeReposBinary() + { + const string firstBranchFileName = "first branch file.bin"; + const string secondBranchFileName = "second branch file.bin"; + const string sharedBranchFileName = "first+second branch file.bin"; + + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var firstBranch = repo.CreateBranch("FirstBranch"); + 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"); + // Commit with ONE new file to first branch (FirstBranch moves forward as it is checked out, SecondBranch stays back one). + AddFileCommitToRepo(repo, firstBranchFileName); + AddFileCommitToRepo(repo, sharedBranchFileName, "\0The first branches comment\0"); // Change file in first branch + + 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 + + MergeResult mergeResult = repo.Merge(repo.Branches["FirstBranch"].Tip, Constants.Signature); + + Assert.Equal(MergeStatus.Conflicts, mergeResult.Status); + + Assert.Single(repo.Index.Conflicts); + + Conflict conflict = repo.Index.Conflicts.First(); + + var changes = repo.Diff.Compare(repo.Lookup(conflict.Theirs.Id), repo.Lookup(conflict.Ours.Id)); + + Assert.True(changes.IsBinaryComparison); + } + } + + [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)] + [InlineData(false, FastForwardStrategy.Default, fastForwardBranchInitialId, MergeStatus.FastForward)] + [InlineData(false, FastForwardStrategy.FastForwardOnly, fastForwardBranchInitialId, MergeStatus.FastForward)] + public void CanFastForwardCommit(bool fromDetachedHead, FastForwardStrategy fastForwardStrategy, string expectedCommitId, MergeStatus expectedMergeStatus) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + if (fromDetachedHead) + { + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); + } + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = fastForwardStrategy }); + + Assert.Equal(expectedMergeStatus, result.Status); + Assert.Equal(expectedCommitId, result.Commit.Id.Sha); + Assert.False(repo.RetrieveStatus().Any()); + Assert.Equal(fromDetachedHead, repo.Info.IsHeadDetached); + } + } + + [Theory] + [InlineData(true, FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData(true, FastForwardStrategy.NoFastForward, MergeStatus.NonFastForward)] + [InlineData(false, FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData(false, FastForwardStrategy.NoFastForward, MergeStatus.NonFastForward)] + public void CanNonFastForwardMergeCommit(bool fromDetachedHead, FastForwardStrategy fastForwardStrategy, MergeStatus expectedMergeStatus) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + if (fromDetachedHead) + { + Commands.Checkout(repo, repo.Head.Tip.Id.Sha); + } + + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = fastForwardStrategy }); + + Assert.Equal(expectedMergeStatus, result.Status); + Assert.False(repo.RetrieveStatus().Any()); + Assert.Equal(fromDetachedHead, repo.Info.IsHeadDetached); + } + } + + [Fact] + public void MergeReportsCheckoutProgress() + { + string repoPath = SandboxMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + + bool wasCalled = false; + + MergeOptions options = new MergeOptions() + { + OnCheckoutProgress = (path, completed, total) => wasCalled = true, + }; + + repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + } + } + + [Fact] + public void MergeReportsCheckoutNotifications() + { + string repoPath = SandboxMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + + bool wasCalled = false; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + MergeOptions options = new MergeOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); + } + } + + [Fact] + public void FastForwardMergeReportsCheckoutProgress() + { + string repoPath = SandboxMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + bool wasCalled = false; + + MergeOptions options = new MergeOptions() + { + OnCheckoutProgress = (path, completed, total) => wasCalled = true, + }; + + repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + } + } + + [Fact] + public void FastForwardMergeReportsCheckoutNotifications() + { + string repoPath = SandboxMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + bool wasCalled = false; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + MergeOptions options = new MergeOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + repo.Merge(commitToMerge, Constants.Signature, options); + + Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); + } + } + + [Fact] + public void MergeCanDetectRenames() + { + // The environment is set up such that: + // file b.txt is edited in the "rename" branch and + // edited and renamed in the "rename_source" branch. + // The edits are automergable. + // We can rename "rename_source" into "rename" + // if rename detection is enabled, + // but the merge will fail with conflicts if this + // change is not detected as a rename. + + string repoPath = SandboxMergeTestRepo(); + using (var repo = new Repository(repoPath)) + { + Branch currentBranch = Commands.Checkout(repo, "rename_source"); + Assert.NotNull(currentBranch); + + Branch branchToMerge = repo.Branches["rename"]; + + MergeResult result = repo.Merge(branchToMerge, Constants.Signature); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + } + } + + [Fact] + public void FastForwardNonFastForwardableMergeThrows() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + Assert.Throws(() => repo.Merge(commitToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = FastForwardStrategy.FastForwardOnly })); + } + } + + [Fact] + public void CanForceFastForwardMergeThroughConfig() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Config.Set("merge.ff", "only"); + + Commit commitToMerge = repo.Branches["normal_merge"].Tip; + Assert.Throws(() => repo.Merge(commitToMerge, Constants.Signature, new MergeOptions())); + } + } + + [Fact] + public void CanMergeAndNotCommit() + { + 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 }); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + Assert.Null(result.Commit); + + RepositoryStatus repoStatus = repo.RetrieveStatus(); + + // Verify that there is a staged entry. + Assert.Single(repoStatus); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("b.txt")); + } + } + + [Fact] + public void CanForceNonFastForwardMerge() + { + 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.NoFastForward }); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + Assert.Equal("f58f780d5a0ae392efd4a924450b1bbdc0577d32", result.Commit.Id.Sha); + Assert.False(repo.RetrieveStatus().Any()); + } + } + + [Fact] + public void CanForceNonFastForwardMergeThroughConfig() + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + repo.Config.Set("merge.ff", "false"); + + Commit commitToMerge = repo.Branches["fast_forward"].Tip; + + MergeResult result = repo.Merge(commitToMerge, Constants.Signature, new MergeOptions()); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + Assert.Equal("f58f780d5a0ae392efd4a924450b1bbdc0577d32", result.Commit.Id.Sha); + Assert.False(repo.RetrieveStatus().Any()); + } + } + + [Fact] + public void VerifyUpToDateMerge() + { + 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.NoFastForward }); + + Assert.Equal(MergeStatus.UpToDate, result.Status); + Assert.Null(result.Commit); + Assert.False(repo.RetrieveStatus().Any()); + } + } + + [Theory] + [InlineData("refs/heads/normal_merge", FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData("normal_merge", FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData("625186280ed2a6ec9b65d250ed90cf2e4acef957", FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData("fast_forward", FastForwardStrategy.Default, MergeStatus.FastForward)] + public void CanMergeCommittish(string committish, FastForwardStrategy strategy, MergeStatus expectedMergeStatus) + { + 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.RetrieveStatus().Any()); + } + } + + [Theory] + [InlineData(true, FastForwardStrategy.FastForwardOnly)] + [InlineData(false, FastForwardStrategy.FastForwardOnly)] + [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 checkout conflicts. + string committishToMerge = "fast_forward"; + + using (var repo = new Repository(SandboxMergeTestRepo())) + { + Touch(repo.Info.WorkingDirectory, "b.txt", "this is an alternate change"); + + if (shouldStage) + { + Commands.Stage(repo, "b.txt"); + } + + Assert.Throws(() => repo.Merge(committishToMerge, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy })); + } + } + + [Theory] + [InlineData(CheckoutFileConflictStrategy.Ours)] + [InlineData(CheckoutFileConflictStrategy.Theirs)] + public void CanSpecifyConflictFileStrategy(CheckoutFileConflictStrategy conflictStrategy) + { + const string conflictFile = "a.txt"; + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + MergeOptions mergeOptions = new MergeOptions() + { + FileConflictStrategy = conflictStrategy, + }; + + MergeResult result = repo.Merge(branch, Constants.Signature, mergeOptions); + Assert.Equal(MergeStatus.Conflicts, result.Status); + + // Get the information on the conflict. + Conflict conflict = repo.Index.Conflicts[conflictFile]; + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch (conflictStrategy) + { + case CheckoutFileConflictStrategy.Theirs: + expectedBlob = repo.Lookup(conflict.Theirs.Id); + break; + case CheckoutFileConflictStrategy.Ours: + expectedBlob = repo.Lookup(conflict.Ours.Id); + break; + default: + throw new Exception("Unexpected FileConflictStrategy"); + } + + Assert.NotNull(expectedBlob); + + // 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))); + } + } + + [Theory] + [InlineData(MergeFileFavor.Ours)] + [InlineData(MergeFileFavor.Theirs)] + public void MergeCanSpecifyMergeFileFavorOption(MergeFileFavor fileFavorFlag) + { + const string conflictFile = "a.txt"; + const string conflictBranchName = "conflicts"; + + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[conflictBranchName]; + Assert.NotNull(branch); + + MergeOptions mergeOptions = new MergeOptions() + { + MergeFileFavor = fileFavorFlag, + }; + + MergeResult result = repo.Merge(branch, Constants.Signature, mergeOptions); + + Assert.Equal(MergeStatus.NonFastForward, result.Status); + + // Verify that the index and working directory are clean + Assert.True(repo.Index.IsFullyMerged); + Assert.False(repo.RetrieveStatus().IsDirty); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch (fileFavorFlag) + { + case MergeFileFavor.Theirs: + expectedBlob = repo.Lookup("3dd9738af654bbf1c363f6c3bbc323bacdefa179"); + break; + case MergeFileFavor.Ours: + expectedBlob = repo.Lookup("610b16886ca829cebd2767d9196f3c4378fe60b5"); + break; + default: + throw new Exception("Unexpected MergeFileFavor"); + } + + Assert.NotNull(expectedBlob); + + // Verify the index has the expected contents + IndexEntry entry = repo.Index[conflictFile]; + Assert.NotNull(entry); + Assert.Equal(expectedBlob.Id, entry.Id); + + // Verify 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))); + } + } + + [Theory] + [InlineData("refs/heads/normal_merge", FastForwardStrategy.Default, MergeStatus.NonFastForward)] + [InlineData("fast_forward", FastForwardStrategy.Default, MergeStatus.FastForward)] + public void CanMergeBranch(string branchName, FastForwardStrategy strategy, MergeStatus expectedMergeStatus) + { + string path = SandboxMergeTestRepo(); + using (var repo = new Repository(path)) + { + Branch branch = repo.Branches[branchName]; + MergeResult result = repo.Merge(branch, Constants.Signature, new MergeOptions() { FastForwardStrategy = strategy }); + + Assert.Equal(expectedMergeStatus, result.Status); + Assert.False(repo.RetrieveStatus().Any()); + } + } + + [Fact] + public void CanMergeIntoOrphanedBranch() + { + 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.RetrieveStatus()) + { + Commands.Unstage(repo, entry.FilePath); + Commands.Remove(repo, entry.FilePath, true); + } + + // Assert that we have an empty working directory. + 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.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); + } + } + } + + private Commit AddFileCommitToRepo(IRepository repository, string filename, string content = null) + { + Touch(repository.Info.WorkingDirectory, filename, content); + + Commands.Stage(repository, filename); + + return repository.Commit("New commit", Constants.Signature, Constants.Signature); + } + + // Commit IDs of the checked in merge_testrepo + private const string masterBranchInitialId = "83cebf5389a4adbcb80bda6b68513caee4559802"; + private const string fastForwardBranchInitialId = "4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53"; } } diff --git a/LibGit2Sharp.Tests/MetaFixture.cs b/LibGit2Sharp.Tests/MetaFixture.cs index 98e3712fc..1d0a1d101 100644 --- a/LibGit2Sharp.Tests/MetaFixture.cs +++ b/LibGit2Sharp.Tests/MetaFixture.cs @@ -1,37 +1,93 @@ -using System.Diagnostics; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; +using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using System.Reflection; -using System; -using System.Linq; -using System.Collections.Generic; namespace LibGit2Sharp.Tests { public class MetaFixture { - private static readonly Type[] excludedTypes = new[] - { - typeof(Credentials), - typeof(Filter), - typeof(ObjectId), - typeof(Repository), - typeof(RepositoryOptions), - typeof(Signature), + private static readonly HashSet explicitOnlyInterfaces = new HashSet + { + 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() + { + var exceptions = new[] + { + "LibGit2Sharp.Tests.FilterBranchFixture.Dispose", + }; + + 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 + where !m.GetCustomAttributes(typeof(FactAttribute), false) + .Concat(m.GetCustomAttributes(typeof(TheoryAttribute), false)) + .Any() + let name = t.FullName + "." + m.Name + where !exceptions.Contains(name) + select name; + + Assert.Equal("", string.Join(Environment.NewLine, fixtures.ToArray())); + } + // Related to https://github.com/libgit2/libgit2sharp/pull/251 [Fact] public void TypesInLibGit2DecoratedWithDebuggerDisplayMustFollowTheStandardImplPattern() { var typesWithDebuggerDisplayAndInvalidImplPattern = new List(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(Repository)).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}") { @@ -54,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)); } } @@ -66,16 +122,16 @@ public void TypesInLibGit2SharpMustBeExtensibleInATestingContext() { var nonTestableTypes = new Dictionary>(); - IEnumerable libGit2SharpTypes = Assembly.GetAssembly(typeof(Repository)).GetExportedTypes() - .Where(t => !excludedTypes.Contains(t) && t.Namespace == typeof(Repository).Namespace && !t.IsSubclassOf(typeof(Delegate))); + 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; @@ -85,19 +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.Count != 0) + { + Assert.Fail(Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes)); } + } - if (nonTestableTypes.Any()) + private static bool MustBeMockable(Type type) + { + if (type.GetTypeInfo().IsSealed) { - Assert.True(false, Environment.NewLine + BuildNonTestableTypesMessage(nonTestableTypes)); + return false; } + + if (type.GetTypeInfo().IsAbstract) + { + return !type.GetTypeInfo().Assembly.GetExportedTypes() + .Where(t => t.GetTypeInfo().IsSubclassOf(type)) + .All(t => t.GetTypeInfo().IsAbstract || t.GetTypeInfo().IsSealed); + } + + return true; } + [Fact] public void EnumsWithFlagsHaveMutuallyExclusiveValues() { - var flagsEnums = Assembly.GetAssembly(typeof(Repository)).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) @@ -133,7 +229,7 @@ private static string BuildNonTestableTypesMessage(Dictionary + 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 => + m.ReturnType.Name == "IEnumerator`1" && + (!m.IsVirtual || m.IsFinal)) + .ToList(); + + 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) + { + 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()); + } + + [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/MockedRepositoryFixture.cs b/LibGit2Sharp.Tests/MockedRepositoryFixture.cs deleted file mode 100644 index eb552f823..000000000 --- a/LibGit2Sharp.Tests/MockedRepositoryFixture.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System.Collections.Generic; -using System; -using System.Linq; -using LibGit2Sharp.Tests.TestHelpers; -using Xunit; -using Moq; - -namespace LibGit2Sharp.Tests -{ - // This fixture shows how one can mock the IRepository when writing an application against LibGit2Sharp. - // The application we want to test is simulated by the CommitCounter class (see below), which takes an IRepository, - // and whose role is to compute and return the number of commits in the given repository. - public class MockedRepositoryFixture : BaseFixture - { - // In this test, we pass to CommitCounter a concrete instance of the Repository. It means we will end up calling the concrete Repository - // during the test run. - [Fact] - public void CanCountCommitsWithConcreteRepository() - { - using (var repo = new Repository(BareTestRepoPath)) - { - var commitCounter = new CommitCounter(repo); - Assert.Equal(7, commitCounter.NumberOfCommits); - } - } - - // This test shows that CommitCounter can take a mocked instance of IRepository. It means we can test CommitCounter without - // relying on the concrete repository. We are testing CommitCounter in isolation. - [Fact] - public void CanCountCommitsWithMockedRepository() - { - var commitLog = Mock.Of(cl => cl.GetEnumerator() == FakeCommitLog(17)); - var repo = Mock.Of(r => r.Commits == commitLog); - - var commitCounter = new CommitCounter(repo); - Assert.Equal(17, commitCounter.NumberOfCommits); - } - - private static IEnumerator FakeCommitLog(int size) - { - for (int i = 0; i < size; i++) - { - yield return FakeCommit(Guid.NewGuid().ToString()); - } - } - - private static Commit FakeCommit(string sha) - { - var commitMock = new Mock(); - commitMock.SetupGet(x => x.Sha).Returns(sha); - - return commitMock.Object; - } - - // Simulated external application ;) - private class CommitCounter - { - private readonly IRepository repo; - - public CommitCounter(IRepository repo) - { - this.repo = repo; - } - - public int NumberOfCommits - { - get { return repo.Commits.Count(); } - } - } - } -} diff --git a/LibGit2Sharp.Tests/MockingFixture.cs b/LibGit2Sharp.Tests/MockingFixture.cs new file mode 100644 index 000000000..6db7dc645 --- /dev/null +++ b/LibGit2Sharp.Tests/MockingFixture.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Moq; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + // This fixture shows how one can mock various LibGit2Sharp APIs. + public class MockingFixture : BaseFixture + { + // The application we want to test is simulated by the CommitCounter class (see below), which takes an IRepository, + // and whose role is to compute and return the number of commits in the given repository. + + // In this test, we pass to CommitCounter a concrete instance of the Repository. It means we will end up calling the concrete Repository + // during the test run. + [Fact] + public void CanCountCommitsWithConcreteRepository() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var commitCounter = new CommitCounter(repo); + Assert.Equal(7, commitCounter.NumberOfCommits); + } + } + + // This test shows that CommitCounter can take a mocked instance of IRepository. It means we can test CommitCounter without + // relying on the concrete repository. We are testing CommitCounter in isolation. + [Fact] + public void CanCountCommitsWithMockedRepository() + { + var commitLog = Mock.Of(cl => cl.GetEnumerator() == FakeCommitLog(17)); + var repo = Mock.Of(r => r.Commits == commitLog); + + var commitCounter = new CommitCounter(repo); + Assert.Equal(17, commitCounter.NumberOfCommits); + } + + private static IEnumerator FakeCommitLog(int size) + { + for (int i = 0; i < size; i++) + { + yield return FakeCommit(Guid.NewGuid().ToString()); + } + } + + private static Commit FakeCommit(string sha) + { + var commitMock = new Mock(); + commitMock.SetupGet(x => x.Sha).Returns(sha); + + return commitMock.Object; + } + + // Simulated external application ;) + private class CommitCounter + { + private readonly IRepository repo; + + public CommitCounter(IRepository repo) + { + this.repo = repo; + } + + public int NumberOfCommits + { + get { return repo.Commits.Count(); } + } + } + + [Fact] + public void CanFakeConfigurationBuildSignature() + { + const string name = "name"; + const string email = "email"; + var now = DateTimeOffset.UtcNow; + + var fakeConfig = new Mock(); + fakeConfig.Setup(c => c.BuildSignature(now)) + .Returns(t => new Signature(name, email, t)); + + var sig = fakeConfig.Object.BuildSignature(now); + Assert.Equal(name, sig.Name); + Assert.Equal(email, sig.Email); + Assert.Equal(now, sig.When); + } + + [Fact] + public void CanFakeEnumerationOfConfiguration() + { + var fakeConfig = new Mock(); + fakeConfig.Setup(c => c.GetEnumerator()).Returns(FakeEntries); + + Assert.Equal(2, fakeConfig.Object.Count()); + } + + private static IEnumerator> FakeEntries() + { + yield return FakeConfigurationEntry("foo", "bar", ConfigurationLevel.Local); + yield return FakeConfigurationEntry("baz", "quux", ConfigurationLevel.Global); + } + + private static ConfigurationEntry FakeConfigurationEntry(string key, string value, ConfigurationLevel level) + { + return new Mock>(key, value, level).Object; + } + } +} diff --git a/LibGit2Sharp.Tests/NetworkFixture.cs b/LibGit2Sharp.Tests/NetworkFixture.cs index 87c9f25cb..f4ad922f6 100644 --- a/LibGit2Sharp.Tests/NetworkFixture.cs +++ b/LibGit2Sharp.Tests/NetworkFixture.cs @@ -1,9 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Tests.TestHelpers; using Xunit; -using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -12,25 +11,67 @@ 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"; - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { Remote remote = repo.Network.Remotes.Add(remoteName, url); - IEnumerable references = repo.Network.ListReferences(remote); + IList references = repo.Network.ListReferences(remote).ToList(); + + + foreach (var reference in references) + { + // None of those references point to an existing + // object in this brand new repository + 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); + } + } + } + + [Theory] + [InlineData("http://github.com/libgit2/TestGitRepository")] + [InlineData("https://github.com/libgit2/TestGitRepository")] + public void CanListRemoteReferencesFromUrl(string url) + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + IList references = repo.Network.ListReferences(url).ToList(); + + foreach (var reference in references) + { + // None of those references point to an existing + // object in this brand new repository + Assert.Null(reference.ResolveToDirectReference().Target); + } + + List> actualRefs = references. + Select(directRef => new Tuple(directRef.CanonicalName, directRef.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); } } } @@ -38,59 +79,220 @@ public void CanListRemoteReferences(string url) [Fact] public void CanListRemoteReferenceObjects() { - string url = "http://github.com/libgit2/TestGitRepository"; - string remoteName = "origin"; + const string url = "http://github.com/libgit2/TestGitRepository"; + const string remoteName = "origin"; var scd = BuildSelfCleaningDirectory(); - using (Repository repo = Repository.Clone(url, scd.RootedDirectoryPath)) + + string clonedRepoPath = Repository.Clone(url, scd.DirectoryPath); + + 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(); - List> 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(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].Item1, actualRefs[i].Item1); + Assert.Equal(TestRemoteRefs.ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + } + } + } + + [SkippableFact] + public void CanListRemoteReferencesWithCredentials() + { + InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), + "Populate Constants.PrivateRepo* to run this test"); + + string remoteName = "origin"; + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + Remote remote = repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); + + var references = repo.Network.ListReferences(remote, Constants.PrivateRepoCredentials); + + foreach (var reference in references) + { + Assert.NotNull(reference); + } + } + } + + [Theory] + [InlineData(FastForwardStrategy.Default)] + [InlineData(FastForwardStrategy.NoFastForward)] + public void CanPull(FastForwardStrategy fastForwardStrategy) + { + 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); + + PullOptions pullOptions = new PullOptions() + { + MergeOptions = new MergeOptions() + { + FastForwardStrategy = fastForwardStrategy + } + }; + + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, pullOptions); + + if (fastForwardStrategy == FastForwardStrategy.Default || fastForwardStrategy == FastForwardStrategy.FastForwardOnly) + { + 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(MergeStatus.NonFastForward, mergeResult.Status); } + } + } + + [Fact] + public void CanPullIntoEmptyRepo() + { + string url = "https://github.com/libgit2/TestGitRepository"; + string remoteName = "origin"; + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + // Set up remote + repo.Network.Remotes.Add(remoteName, url); + + // Set up tracking information + repo.Branches.Update(repo.Head, + b => b.Remote = remoteName, + b => b.UpstreamBranch = "refs/heads/master"); + + // Pull! + MergeResult mergeResult = Commands.Pull(repo, Constants.Signature, new PullOptions()); - Assert.Equal(ExpectedRemoteRefs.Count, actualRefs.Count); - for (int i = 0; i < ExpectedRemoteRefs.Count; i++) + 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); + } + } + + [Fact] + public void PullWithoutMergeBranchThrows() + { + var scd = BuildSelfCleaningDirectory(); + string clonedRepoPath = Repository.Clone(StandardTestRepoPath, scd.DirectoryPath); + + using (var repo = new Repository(clonedRepoPath)) + { + Branch branch = repo.Branches["master"]; + + // Update the Upstream merge branch + repo.Branches.Update(branch, + b => b.UpstreamBranch = "refs/heads/another_master"); + + bool didPullThrow = false; + MergeFetchHeadNotFoundException thrownException = null; + + try { - Assert.Equal(ExpectedRemoteRefs[i].Item1, actualRefs[i].Item1); - Assert.Equal(ExpectedRemoteRefs[i].Item2, actualRefs[i].Item2); + Commands.Pull(repo, Constants.Signature, new PullOptions()); } + catch (MergeFetchHeadNotFoundException ex) + { + didPullThrow = true; + thrownException = ex; + } + + Assert.True(didPullThrow, "Pull did not throw."); + Assert.True(thrownException.Message.Contains("refs/heads/another_master"), "Exception message did not contain expected reference."); } } - /* - * 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() + { + 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() { - 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); + + 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 02dcd1858..4f23ced5c 100644 --- a/LibGit2Sharp.Tests/NoteFixture.cs +++ b/LibGit2Sharp.Tests/NoteFixture.cs @@ -1,7 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -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,26 +56,39 @@ 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")]; Assert.NotNull(notes); - Assert.Equal(3, notes.Count()); - Assert.Equal(expectedMessages, notes.Select(n => n.Message)); + Assert.Equal(expectedMessages, SortedNotes(notes, n => n.Message)); + } + } + + [Fact] + public void CanRetrieveASpecificNoteFromAKnownNamespace() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var singleNote = repo.Notes["answer", new ObjectId("4a202b346bb0fb0db7eff3cffeb3c70babbd2045")]; + Assert.Equal("Nope\n", singleNote.Message); } } [Fact] public void CanGetListOfNotesNamespaces() { - var expectedNamespaces = new[] { "commits", "answer", "answer2" }; + 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); + Assert.Equal(expectedNamespaces, + repo.Notes.Namespaces.OrderBy(n => n, StringComparer.Ordinal).ToArray()); Assert.Equal(repo.Notes.DefaultNamespace, repo.Notes.Namespaces.First()); } } @@ -100,25 +115,25 @@ public void CanAccessNotesFromACommit() { var expectedNamespaces = new[] { "Just Note, don't you understand?\n", "Nope\n", "Not Nope, Note!\n" }; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("4a202b346bb0fb0db7eff3cffeb3c70babbd2045"); - Assert.Equal(expectedNamespaces, commit.Notes.Select(n => n.Message)); + Assert.Equal(expectedNamespaces, SortedNotes(commit.Notes, n => n.Message)); // Make sure that Commit.Notes is not refreshed automatically repo.Notes.Add(commit.Id, "I'm batman!\n", signatureNullToken, signatureYorah, "batmobile"); - Assert.Equal(expectedNamespaces, commit.Notes.Select(n => n.Message)); + Assert.Equal(expectedNamespaces, SortedNotes(commit.Notes, m => m.Message)); } } [Fact] public void CanAddANoteOnAGitObject() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); var note = repo.Notes.Add(commit.Id, "I'm batman!\n", signatureNullToken, signatureYorah, "batmobile"); @@ -134,8 +149,8 @@ public void CanAddANoteOnAGitObject() [Fact] public void CreatingANoteWhichAlreadyExistsOverwritesThePreviousNote() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); Assert.NotNull(commit.Notes.FirstOrDefault(x => x.Namespace == "answer")); @@ -150,11 +165,35 @@ public void CreatingANoteWhichAlreadyExistsOverwritesThePreviousNote() } } + [Fact] + public void CanAddANoteWithSignatureFromConfig() + { + string path = SandboxBareTestRepo(); + + using (var repo = new Repository(path)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); + + 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); + + Assert.Equal("I'm batman!\n", newNote.Message); + Assert.Equal("batmobile", newNote.Namespace); + + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/batmobile"), Constants.Identity); + } + } + [Fact] public void CanCompareTwoUniqueNotes() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("9fd738e8f7967c078dceed8190330fc8648ee56a"); @@ -188,8 +227,8 @@ public void CanCompareTwoUniqueNotes() [Fact] public void CanRemoveANoteFromAGitObject() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("8496071c1b46c854b31185ea97743be6a8774479"); var notes = repo.Notes[commit.Id]; @@ -216,8 +255,8 @@ public void CanRemoveANoteFromAGitObject() [Fact] public void RemovingANonExistingNoteDoesntThrow() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var commit = repo.Lookup("5b5b025afb0b4c913b4c338a42934a3863bf3644"); @@ -225,19 +264,68 @@ public void RemovingANonExistingNoteDoesntThrow() } } + [Fact] + public void CanRemoveANoteWithSignatureFromConfig() + { + string path = SandboxBareTestRepo(); + + using (var repo = new Repository(path)) + { + CreateConfigurationWithDummyUser(repo, Constants.Identity); + var commit = repo.Lookup("8496071c1b46c854b31185ea97743be6a8774479"); + var notes = repo.Notes[commit.Id]; + + Assert.NotEmpty(notes); + + Signature signature = repo.Config.BuildSignature(DateTimeOffset.Now); + + repo.Notes.Remove(commit.Id, signature, signature, repo.Notes.DefaultNamespace); + + Assert.Empty(notes); + + AssertCommitIdentitiesAre(repo.Lookup("refs/notes/" + repo.Notes.DefaultNamespace), Constants.Identity); + } + } + [Fact] public void CanRetrieveTheListOfNotesForAGivenNamespace() { - var expectedNotes = new[] { new Tuple("1a550e416326cdb4a8e127a04dd69d7a01b11cf4", "4a202b346bb0fb0db7eff3cffeb3c70babbd2045"), - new Tuple("272a41cf2b22e57f2bc5bf6ef37b63568cd837e4", "8496071c1b46c854b31185ea97743be6a8774479") }; + var expectedNotes = new[] + { + new { Blob = "272a41cf2b22e57f2bc5bf6ef37b63568cd837e4", Target = "8496071c1b46c854b31185ea97743be6a8774479" }, + new { Blob = "1a550e416326cdb4a8e127a04dd69d7a01b11cf4", Target = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045" }, + }; - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(expectedNotes, repo.Notes["commits"].Select(n => new Tuple(n.BlobId.Sha, n.TargetObjectId.Sha)).ToArray()); + Assert.Equal(expectedNotes, + SortedNotes(repo.Notes["commits"], n => new { Blob = n.BlobId.Sha, Target = n.TargetObjectId.Sha })); Assert.Equal("commits", repo.Notes.DefaultNamespace); - Assert.Equal(expectedNotes, repo.Notes.Select(n => new Tuple(n.BlobId.Sha, n.TargetObjectId.Sha)).ToArray()); + Assert.Equal(expectedNotes, + SortedNotes(repo.Notes, n => new { Blob = n.BlobId.Sha, Target = n.TargetObjectId.Sha })); } } + + [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 bb565bd9e..34d3eb77c 100644 --- a/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs +++ b/LibGit2Sharp.Tests/ObjectDatabaseFixture.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -16,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); @@ -27,11 +29,10 @@ public void CanTellIfObjectsExists(string sha, bool shouldExists) [Fact] public void CanCreateABlobFromAFileInTheWorkingDirectory() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(scd.DirectoryPath)) + 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,18 +49,35 @@ 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() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); + string path = InitNewRepository(); SelfCleaningDirectory directory = BuildSelfCleaningDirectory(); - Directory.CreateDirectory(directory.RootedDirectoryPath); - string filepath = Path.Combine(directory.RootedDirectoryPath, "hello.txt"); - File.WriteAllText(filepath, "I'm a new file\n"); + string filepath = Touch(directory.RootedDirectoryPath, "hello.txt", "I'm a new file\n"); - using (var repo = new Repository(scd.RepositoryPath)) + using (var repo = new Repository(path)) { /* * $ echo "I'm a new file" | git hash-object --stdin @@ -71,7 +89,7 @@ public void CanCreateABlobIntoTheDatabaseOfABareRepository() Assert.NotNull(blob); Assert.Equal("dc53d4c6b8684c21b0b57db29da4a2afea011565", blob.Sha); - Assert.Equal("I'm a new file\n", blob.ContentAsUtf8()); + Assert.Equal("I'm a new file\n", blob.GetContentText()); var fetchedBlob = repo.Lookup(blob.Id); Assert.Equal(blob, fetchedBlob); @@ -83,35 +101,121 @@ public void CanCreateABlobIntoTheDatabaseOfABareRepository() [InlineData("321cbdf08803c744082332332838df6bd160f8f9", "dummy.data")] [InlineData("e9671e138a780833cb689753570fd10a55be84fb", "dummy.txt")] [InlineData("e9671e138a780833cb689753570fd10a55be84fb", "dummy.guess")] - public void CanCreateABlobFromABinaryReader(string expectedSha, string hintPath) + public void CanCreateABlobFromAStream(string expectedSha, string hintPath) { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - var sb = new StringBuilder(); for (int i = 0; i < 6; i++) { sb.Append("libgit2\n\r\n"); } - using (var repo = new Repository(scd.RepositoryPath)) + using (var repo = new Repository(InitNewRepository())) { CreateAttributesFiles(Path.Combine(repo.Info.Path, "info"), "attributes"); using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString()))) - using (var binReader = new BinaryReader(stream)) { - Blob blob = repo.ObjectDatabase.CreateBlob(binReader, hintPath); + Blob blob = repo.ObjectDatabase.CreateBlob(stream, hintPath); Assert.Equal(expectedSha, blob.Sha); } } } + [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(); + for (int i = 0; i < contentSize; i++) + { + sb.Append(i % 10); + } + + return new MemoryStream(Encoding.UTF8.GetBytes(sb.ToString())); + } + + [Theory] + [InlineData(34, 8)] + [InlineData(7584, 5879)] + [InlineData(7854, 1247)] + [InlineData(8192, 4096)] + [InlineData(8192, 4095)] + [InlineData(8192, 4097)] + public void CanCreateABlobFromAStreamWithANumberOfBytesToConsume(int contentSize, int numberOfBytesToConsume) + { + string path = InitNewRepository(); + + + using (var repo = new Repository(path)) + { + using (var stream = PrepareMemoryStream(contentSize)) + { + Blob blob = repo.ObjectDatabase.CreateBlob(stream, numberOfBytesToConsume: numberOfBytesToConsume); + Assert.Equal(numberOfBytesToConsume, blob.Size); + } + } + } + + [Theory] + [InlineData(16, 32, null)] + [InlineData(7854, 9785, null)] + [InlineData(16, 32, "binary.bin")] + [InlineData(7854, 9785, "binary.bin")] + public void CreatingABlobFromTooShortAStreamThrows(int contentSize, int numberOfBytesToConsume, string hintpath) + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + { + using (var stream = PrepareMemoryStream(contentSize)) + { + Assert.Throws(() => repo.ObjectDatabase.CreateBlob(stream, hintpath, numberOfBytesToConsume)); + } + } + } + + [Fact] + public void CreatingABlobFromANonReadableStreamThrows() + { + string path = InitNewRepository(); + + using (var repo = new Repository(path)) + using (var stream = new FileStream( + Path.Combine(repo.Info.WorkingDirectory, "file.txt"), + FileMode.CreateNew, FileAccess.Write)) + { + Assert.Throws(() => repo.ObjectDatabase.CreateBlob(stream)); + } + } + private static void CreateAttributesFiles(string where, string filename) { const string attributes = "* text=auto\n*.txt text\n*.data binary\n"; - Directory.CreateDirectory(where); - File.WriteAllText(Path.Combine(where, filename), attributes); + Touch(where, filename, attributes); } [Theory] @@ -122,9 +226,8 @@ private static void CreateAttributesFiles(string where, string filename) [InlineData("1")] public void CanCreateATreeByAlteringAnExistingOne(string targetPath) { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var blob = repo.Lookup(new ObjectId("a8233120f6ad708f843d861ce2b7228ec4e3dec6")); @@ -139,9 +242,8 @@ public void CanCreateATreeByAlteringAnExistingOne(string targetPath) [Fact] public void CanCreateATreeByRemovingEntriesFromExistingOne() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree) .Remove("branch_file.txt") @@ -161,9 +263,8 @@ public void CanCreateATreeByRemovingEntriesFromExistingOne() [Fact] public void RemovingANonExistingEntryFromATreeDefinitionHasNoSideEffect() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tree head = repo.Head.Tip.Tree; @@ -184,9 +285,8 @@ public void RemovingANonExistingEntryFromATreeDefinitionHasNoSideEffect() [Fact] public void CanCreateAnEmptyTree() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = InitNewRepository(); + using (var repo = new Repository(path)) { var td = new TreeDefinition(); @@ -199,12 +299,11 @@ public void CanCreateAnEmptyTree() [Fact] public void CanReplaceAnExistingTreeWithAnotherPersitedTree() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(scd.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); - Assert.Equal(GitObjectType.Tree, td["1"].Type); + Assert.Equal(TreeEntryTargetType.Tree, td["1"].TargetType); TreeDefinition newTd = new TreeDefinition() .Add("new/one", repo.Lookup("a823312"), Mode.NonExecutableFile) @@ -214,18 +313,17 @@ public void CanReplaceAnExistingTreeWithAnotherPersitedTree() repo.ObjectDatabase.CreateTree(newTd); td.Add("1", newTd["new"]); - Assert.Equal(GitObjectType.Tree, td["1/tree"].Type); + Assert.Equal(TreeEntryTargetType.Tree, td["1/tree"].TargetType); } } [Fact] public void CanCreateATreeContainingABlobFromAFileInTheWorkingDirectory() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(scd.DirectoryPath)) + 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) @@ -251,10 +349,53 @@ public void CanCreateATreeContainingABlobFromAFileInTheWorkingDirectory() } } + [Fact] + public void CanCreateATreeContainingAGitLinkFromAnUntrackedSubmoduleInTheWorkingDirectory() + { + string path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + const string submodulePath = "sm_added_and_uncommited"; + + var submoduleBefore = repo.Submodules[submodulePath]; + Assert.NotNull(submoduleBefore); + Assert.Null(submoduleBefore.HeadCommitId); + + var objectId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; + + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree) + .AddGitLink(submodulePath, objectId); + + TreeEntryDefinition ted = td[submodulePath]; + Assert.NotNull(ted); + Assert.Equal(Mode.GitLink, ted.Mode); + Assert.Equal(objectId, ted.TargetId); + Assert.Equal(TreeEntryTargetType.GitLink, ted.TargetType); + + Tree tree = repo.ObjectDatabase.CreateTree(td); + + TreeEntry te = tree[submodulePath]; + Assert.NotNull(te.Target); + Assert.IsType(te.Target); + Assert.Equal(objectId, te.Target.Id); + + var commitWithSubmodule = repo.ObjectDatabase.CreateCommit(Constants.Signature, Constants.Signature, "Submodule!", + tree, new[] { repo.Head.Tip }, false); + repo.Reset(ResetMode.Soft, commitWithSubmodule); + + var submodule = repo.Submodules[submodulePath]; + Assert.NotNull(submodule); + Assert.Equal(submodulePath, submodule.Name); + Assert.Equal(submodulePath, submodule.Path); + Assert.Equal(objectId, submodule.HeadCommitId); + } + } + [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); @@ -264,11 +405,45 @@ public void CannotCreateATreeContainingABlobFromARelativePathAgainstABareReposit } [Fact] - public void CanCreateACommit() + public void CreatingATreeFromIndexWithUnmergedEntriesThrows() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); + var path = SandboxMergedTestRepo(); + using (var repo = new Repository(path)) + { + Assert.False(repo.Index.IsFullyMerged); - using (var repo = new Repository(scd.RepositoryPath)) + Assert.Throws( + () => repo.ObjectDatabase.CreateTree(repo.Index)); + } + } + + [Fact] + public void CanCreateATreeFromIndex() + { + string path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + const string expectedIndexTreeSha = "0fe0fd1943a1b63ecca36fa6bbe9bbe045f791a4"; + + // The tree representing the index is not in the db. + Assert.Null(repo.Lookup(expectedIndexTreeSha)); + + var tree = repo.ObjectDatabase.CreateTree(repo.Index); + Assert.NotNull(tree); + Assert.Equal(expectedIndexTreeSha, tree.Id.Sha); + + // The tree representing the index is now in the db. + tree = repo.Lookup(expectedIndexTreeSha); + Assert.NotNull(tree); + } + } + + [Fact] + public void CanCreateACommit() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch head = repo.Head; @@ -277,7 +452,7 @@ public void CanCreateACommit() Tree tree = repo.ObjectDatabase.CreateTree(td); - Commit commit = repo.ObjectDatabase.CreateCommit("Ü message", DummySignature, DummySignature, tree, new[] { repo.Head.Tip }); + Commit commit = repo.ObjectDatabase.CreateCommit(Constants.Signature, Constants.Signature, "Ü message", tree, new[] { repo.Head.Tip }, true); Branch newHead = repo.Head; @@ -288,22 +463,310 @@ public void CanCreateACommit() } [Fact] - public void CanCreateABinaryBlobFromABinaryReader() + public void CanCreateABinaryBlobFromAStream() { - TemporaryCloneOfTestRepo scd = BuildTemporaryCloneOfTestRepo(); - var binaryContent = new byte[] { 0, 1, 2, 3, 4, 5 }; - using (var repo = new Repository(scd.RepositoryPath)) + string path = InitNewRepository(); + using (var repo = new Repository(path)) { using (var stream = new MemoryStream(binaryContent)) - using (var binReader = new BinaryReader(stream)) { - Blob blob = repo.ObjectDatabase.CreateBlob(binReader); + Blob blob = repo.ObjectDatabase.CreateBlob(stream); Assert.Equal(6, blob.Size); - Assert.Equal(true, blob.IsBinary); + Assert.True(blob.IsBinary); } } } + + [Fact] + public void CanCreateATagAnnotationPointingToAGitObject() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var blob = repo.Head.Tip["README"].Target as Blob; + Assert.NotNull(blob); + + TagAnnotation tag = repo.ObjectDatabase.CreateTagAnnotation( + "nice_blob", + blob, + Constants.Signature, + "I can point at blobs, too!"); + + Assert.NotNull(tag); + + // The TagAnnotation is not pointed at by any reference... + Assert.Null(repo.Tags["nice_blob"]); + + // ...but exists in the odb. + var fetched = repo.Lookup(tag.Id); + Assert.Equal(tag, fetched); + } + } + + [Fact] + public void CanEnumerateTheGitObjectsFromBareRepository() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + int count = 0; + + foreach (var obj in repo.ObjectDatabase) + { + Assert.NotNull(obj); + count++; + } + + Assert.True(count >= 1683); + } + } + + [Theory] + [InlineData("\0Leading zero")] + [InlineData("Trailing zero\0")] + [InlineData("Zero \0inside")] + [InlineData("\0")] + [InlineData("\0\0\0")] + public void CreatingACommitWithMessageContainingZeroByteThrows(string message) + { + 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)); + } + } + + [Theory] + [InlineData("\0Leading zero")] + [InlineData("Trailing zero\0")] + [InlineData("Zero \0inside")] + [InlineData("\0")] + [InlineData("\0\0\0")] + public void CreatingATagAnnotationWithNameOrMessageContainingZeroByteThrows(string input) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + input, repo.Head.Tip, Constants.Signature, "message")); + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + "name", repo.Head.Tip, Constants.Signature, input)); + } + } + + [Fact] + public void CreatingATagAnnotationWithBadParametersThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + null, repo.Head.Tip, Constants.Signature, "message")); + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + string.Empty, repo.Head.Tip, Constants.Signature, "message")); + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + "name", null, Constants.Signature, "message")); + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + "name", repo.Head.Tip, null, "message")); + Assert.Throws(() => repo.ObjectDatabase.CreateTagAnnotation( + "name", repo.Head.Tip, Constants.Signature, null)); + } + } + + [Fact] + public void CanCreateATagAnnotationWithAnEmptyMessage() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var tagAnnotation = repo.ObjectDatabase.CreateTagAnnotation( + "name", repo.Head.Tip, Constants.Signature, string.Empty); + + Assert.Equal(string.Empty, tagAnnotation.Message); + } + } + + [Theory] + [InlineData("c47800c", "9fd738e", "5b5b025", 1, 2)] + [InlineData("9fd738e", "c47800c", "5b5b025", 2, 1)] + public void CanCalculateHistoryDivergence( + string sinceSha, string untilSha, + string expectedAncestorSha, int? expectedAheadBy, int? expectedBehindBy) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var since = repo.Lookup(sinceSha); + var until = repo.Lookup(untilSha); + + HistoryDivergence div = repo.ObjectDatabase.CalculateHistoryDivergence(since, until); + + Assert.Equal(expectedAheadBy, div.AheadBy); + Assert.Equal(expectedBehindBy, div.BehindBy); + Assert.Equal(expectedAncestorSha, div.CommonAncestor.Id.ToString(7)); + } + } + + [Theory] + [InlineData("c47800c", "41bc8c6907", 3, 2)] + public void CanCalculateHistoryDivergenceWhenNoAncestorIsShared( + string sinceSha, string untilSha, + int? expectedAheadBy, int? expectedBehindBy) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var since = repo.Lookup(sinceSha); + var until = repo.Lookup(untilSha); + + HistoryDivergence div = repo.ObjectDatabase.CalculateHistoryDivergence(since, until); + + Assert.Equal(expectedAheadBy, div.AheadBy); + Assert.Equal(expectedBehindBy, div.BehindBy); + Assert.Null(div.CommonAncestor); + } + } + + [Fact] + public void CalculatingHistoryDivergenceWithBadParamsThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws( + () => repo.ObjectDatabase.CalculateHistoryDivergence(repo.Head.Tip, null)); + Assert.Throws( + () => repo.ObjectDatabase.CalculateHistoryDivergence(null, repo.Head.Tip)); + } + } + + [Fact] + public void CanShortenObjectIdentifier() + { + /* + * $ echo "aabqhq" | git hash-object -t blob --stdin + * dea509d0b3cb8ee0650f6ca210bc83f4678851ba + * + * $ echo "aaazvc" | git hash-object -t blob --stdin + * dea509d097ce692e167dfc6a48a7a280cc5e877e + */ + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + repo.Config.Set("core.abbrev", 4); + + Blob blob1 = CreateBlob(repo, "aabqhq\n"); + Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851ba", blob1.Sha); + + Assert.Equal("dea5", repo.ObjectDatabase.ShortenObjectId(blob1)); + Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12)); + Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851b", repo.ObjectDatabase.ShortenObjectId(blob1, 39)); + + Blob blob2 = CreateBlob(repo, "aaazvc\n"); + Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2)); + Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2, 4)); + Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1)); + Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1, 7)); + + Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12)); + Assert.Equal("dea509d097ce", repo.ObjectDatabase.ShortenObjectId(blob2, 12)); + } + } + + [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))) + { + return repo.ObjectDatabase.CreateBlob(stream); + } + } } } diff --git a/LibGit2Sharp.Tests/ObjectIdFixture.cs b/LibGit2Sharp.Tests/ObjectIdFixture.cs index 280c03723..8d3468bdd 100644 --- a/LibGit2Sharp.Tests/ObjectIdFixture.cs +++ b/LibGit2Sharp.Tests/ObjectIdFixture.cs @@ -39,6 +39,24 @@ public void CanConvertShaToOid() Assert.Equal(bytes, id.RawId); } + [Fact] + public void CanCastShaToObjectId() + { + var id = (ObjectId)validSha1; + + Assert.Equal(bytes, id.RawId); + } + + [Fact] + public void CanCastNullToObjectId() + { + string sha = null; + + var id = (ObjectId)sha; + + Assert.Null(id); + } + [Fact] public void CreatingObjectIdWithWrongNumberOfBytesThrows() { @@ -115,8 +133,24 @@ 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)); } + + [Theory] + [InlineData("d", true)] + [InlineData("dead", true)] + [InlineData("deadbe", true)] + [InlineData("DeAdBEE", true)] + [InlineData("deff", false)] + [InlineData("dfff", false)] + [InlineData("9876", false)] + [InlineData("This is not a valid short hexified sha!!!!!", false)] + public void StartsWith(string shortSha, bool expected) + { + var id = new ObjectId("deadbeef84650f067bd5703b6a59a8b3b3c99a09"); + + Assert.Equal(expected, id.StartsWith(shortSha)); + } } } diff --git a/LibGit2Sharp.Tests/OdbBackendFixture.cs b/LibGit2Sharp.Tests/OdbBackendFixture.cs index 7420c2f4b..65011ce0f 100644 --- a/LibGit2Sharp.Tests/OdbBackendFixture.cs +++ b/LibGit2Sharp.Tests/OdbBackendFixture.cs @@ -1,7 +1,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.Security.Cryptography; +using System.Linq; +using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -9,37 +10,268 @@ namespace LibGit2Sharp.Tests { public class OdbBackendFixture : BaseFixture { + private const string content = "test\n"; + + private static Commit AddCommitToRepo(IRepository repo) + { + string relativeFilepath = "test.txt"; + Touch(repo.Info.WorkingDirectory, relativeFilepath, content); + Commands.Stage(repo, relativeFilepath); + + var ie = repo.Index[relativeFilepath]; + Assert.NotNull(ie); + Assert.Equal("9daeafb9864cf43055ae93beb0afd6c7d144bfa4", ie.Id.Sha); + + var author = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100")); + var commit = repo.Commit("Initial commit", author, author); + + relativeFilepath = "big.txt"; + var zeros = new string('0', 32 * 1024 + 3); + Touch(repo.Info.WorkingDirectory, relativeFilepath, zeros); + Commands.Stage(repo, relativeFilepath); + + ie = repo.Index[relativeFilepath]; + Assert.NotNull(ie); + Assert.Equal("6518215c4274845a759cb498998fe696c42e3e0f", ie.Id.Sha); + + return commit; + } + + private static void AssertGeneratedShas(IRepository repo) + { + Commit commit = repo.Commits.Single(); + Assert.Equal("1fe3126578fc4eca68c193e4a3a0a14a0704624d", commit.Sha); + Tree tree = commit.Tree; + Assert.Equal("2b297e643c551e76cfa1f93810c50811382f9117", tree.Sha); + + GitObject blob = tree.Single().Target; + Assert.IsAssignableFrom(blob); + Assert.Equal("9daeafb9864cf43055ae93beb0afd6c7d144bfa4", blob.Sha); + } + [Fact] - public void SimpleOdbBackendFixtureTest() + public void CanGeneratePredictableObjectShasWithTheDefaultBackend() { - var scd = new SelfCleaningDirectory(this); + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + AddCommitToRepo(repo); + + AssertGeneratedShas(repo); + } + } + + [Fact] + public void CanGeneratePredictableObjectShasWithAProvidedBackend() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + repo.ObjectDatabase.AddBackend(new MockOdbBackend(), priority: 5); + + AddCommitToRepo(repo); + + AssertGeneratedShas(repo); + + var objectId = new ObjectId("9daeafb9864cf43055ae93beb0afd6c7d144bfa4"); + + Assert.True(repo.ObjectDatabase.Contains(objectId)); + + var blob = repo.Lookup(objectId); + Assert.True(content.Length == blob.Size); - using (Repository repository = Repository.Init(scd.RootedDirectoryPath)) + var other = repo.Lookup("9daeaf"); + Assert.Equal(blob, other); + } + } + + [Fact] + public void CanRetrieveObjectsThroughOddSizedShortShas() + { + try { - repository.ObjectDatabase.AddBackend(new MockOdbBackend(), priority: 5); + GlobalSettings.SetStrictHashVerification(false); + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + var backend = new MockOdbBackend(); + repo.ObjectDatabase.AddBackend(backend, priority: 5); + + AddCommitToRepo(repo); - String filePath = Path.Combine(scd.RootedDirectoryPath, "file.txt"); - String fileContents = "Hello!"; + var blob1 = repo.Lookup("9daeaf"); + Assert.NotNull(blob1); - // Exercises read, write, writestream, exists - File.WriteAllText(filePath, fileContents); - repository.Index.Stage(filePath); + const string dummy = "dummy\n"; - var signature = new Signature("SimpleOdbBackendFixtureTest", "user@example.com", DateTimeOffset.Now); - repository.Commit(String.Empty, signature, signature); + // 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)); + } - // Exercises read - var blob = repository.Lookup(new ObjectId("69342c5c39e5ae5f0077aecc32c0f81811fb8193")); + var blob2 = repo.Lookup(fakeId); + Assert.NotNull(blob2); - Assert.NotNull(blob); - Assert.True(fileContents.Length == blob.Size); + Assert.Throws(() => repo.Lookup("9daeaf")); + + var newBlob1 = repo.Lookup("9daeafb"); + var newBlob2 = repo.Lookup("9daeaf0"); + + Assert.Equal(blob1, newBlob1); + Assert.Equal(blob2, newBlob2); + } + } + finally + { + GlobalSettings.SetStrictHashVerification(true); } } + [Fact] + public void CanEnumerateTheContentOfTheObjectDatabase() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + var backend = new MockOdbBackend(); + repo.ObjectDatabase.AddBackend(backend, priority: 5); + + AddCommitToRepo(repo); + + var expected = new[] { "1fe3126", "2b297e6", "6518215", "9daeafb" }; + + IEnumerable objs = repo.ObjectDatabase; + + IEnumerable retrieved = + objs + .Select(o => o.Id.ToString(7)) + .OrderBy(s => s, StringComparer.Ordinal); + + Assert.Equal(expected, retrieved); + } + } + + [Fact] + public void CanPushWithACustomBackend() + { + string remoteRepoPath = InitNewRepository(true); + string localRepoPath = InitNewRepository(); + Commit commit; + + using (var localRepo = new Repository(localRepoPath)) + { + localRepo.ObjectDatabase.AddBackend(new MockOdbBackend(), 5); + + commit = AddCommitToRepo(localRepo); + + Remote remote = localRepo.Network.Remotes.Add("origin", remoteRepoPath); + + localRepo.Branches.Update(localRepo.Head, + b => b.Remote = remote.Name, + b => b.UpstreamBranch = localRepo.Head.CanonicalName); + + localRepo.Network.Push(localRepo.Head); + } + + using (var remoteRepo = new Repository(remoteRepoPath)) + { + Assert.Equal(commit, remoteRepo.Head.Tip); + } + } + + [Fact] + public void CanShortenObjectIdentifier() + { + /* + * $ echo "aabqhq" | git hash-object -t blob --stdin + * dea509d0b3cb8ee0650f6ca210bc83f4678851ba + * + * $ echo "aaazvc" | git hash-object -t blob --stdin + * dea509d097ce692e167dfc6a48a7a280cc5e877e + */ + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + repo.ObjectDatabase.AddBackend(new MockOdbBackend(), 5); + + repo.Config.Set("core.abbrev", 4); + + Blob blob1 = CreateBlob(repo, "aabqhq\n"); + Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851ba", blob1.Sha); + + Assert.Equal("dea5", repo.ObjectDatabase.ShortenObjectId(blob1)); + Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12)); + Assert.Equal("dea509d0b3cb8ee0650f6ca210bc83f4678851b", repo.ObjectDatabase.ShortenObjectId(blob1, 39)); + + Blob blob2 = CreateBlob(repo, "aaazvc\n"); + Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2)); + Assert.Equal("dea509d09", repo.ObjectDatabase.ShortenObjectId(blob2, 4)); + Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1)); + Assert.Equal("dea509d0b", repo.ObjectDatabase.ShortenObjectId(blob1, 7)); + + Assert.Equal("dea509d0b3cb", repo.ObjectDatabase.ShortenObjectId(blob1, 12)); + Assert.Equal("dea509d097ce", repo.ObjectDatabase.ShortenObjectId(blob2, 12)); + } + } + + private static Blob CreateBlob(Repository repo, string content) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + return repo.ObjectDatabase.CreateBlob(stream); + } + } + + [Fact] + public void ADisposableOdbBackendGetsDisposedUponRepositoryDisposal() + { + string path = InitNewRepository(); + + int nbOfDisposeCalls = 0; + + using (var repo = new Repository(path)) + { + var mockOdbBackend = new MockOdbBackend(() => { nbOfDisposeCalls++; }); + + Assert.IsAssignableFrom(mockOdbBackend); + + repo.ObjectDatabase.AddBackend(mockOdbBackend, 5); + + Assert.Equal(0, nbOfDisposeCalls); + } + + Assert.Equal(1, nbOfDisposeCalls); + } + #region MockOdbBackend - private class MockOdbBackend : OdbBackend + private class MockOdbBackend : OdbBackend, IDisposable { + public MockOdbBackend(Action disposer = null) + { + this.disposer = disposer; + } + + public void Dispose() + { + if (disposer == null) + { + return; + } + + disposer(); + + disposer = null; + } + protected override OdbBackendOperations SupportedOperations { get @@ -48,145 +280,193 @@ protected override OdbBackendOperations SupportedOperations OdbBackendOperations.ReadPrefix | OdbBackendOperations.Write | OdbBackendOperations.WriteStream | - OdbBackendOperations.Exists; + OdbBackendOperations.Exists | + OdbBackendOperations.ExistsPrefix | + OdbBackendOperations.ForEach | + OdbBackendOperations.ReadHeader; } } - public override int Read(byte[] oid, out Stream data, out GitObjectType objectType) + public override int Read(ObjectId oid, out UnmanagedMemoryStream data, out ObjectType objectType) { data = null; - objectType = GitObjectType.Bad; + objectType = default(ObjectType); MockGitObject gitObject; - if (m_objectIdToContent.TryGetValue(oid, out gitObject)) + if (!m_objectIdToContent.TryGetValue(oid, out gitObject)) { - data = Allocate(gitObject.Data.LongLength); - data.Write(gitObject.Data, 0, gitObject.Data.Length); + return (int)ReturnCode.GIT_ENOTFOUND; + } - objectType = gitObject.ObjectType; + data = Allocate(gitObject.Length); - return GIT_OK; + foreach (var chunk in gitObject.Data) + { + data.Write(chunk, 0, chunk.Length); } - return GIT_ENOTFOUND; + objectType = gitObject.ObjectType; + + return (int)ReturnCode.GIT_OK; } - public override int ReadPrefix(byte[] shortOid, out byte[] oid, out Stream data, out GitObjectType objectType) + public override int ReadPrefix(string shortSha, out ObjectId id, out UnmanagedMemoryStream data, out ObjectType objectType) { - oid = null; + id = null; data = null; - objectType = GitObjectType.Bad; + objectType = default(ObjectType); - MockGitObject gitObjectAlreadyFound = null; + ObjectId matchingKey = null; - foreach (MockGitObject gitObject in m_objectIdToContent.Values) + foreach (ObjectId objectId in m_objectIdToContent.Keys) { - bool match = true; - - for (int i = 0; i < shortOid.Length; i++) - { - if (gitObject.ObjectId[i] != shortOid[i]) - { - match = false; - break; - } - } - - if (!match) + if (!objectId.StartsWith(shortSha)) { continue; } - if (null != gitObjectAlreadyFound) + if (matchingKey != null) { - return GIT_EAMBIGUOUS; + return (int)ReturnCode.GIT_EAMBIGUOUS; } - gitObjectAlreadyFound = gitObject; + matchingKey = objectId; } - if (null != gitObjectAlreadyFound) + if (matchingKey == null) { - oid = gitObjectAlreadyFound.ObjectId; - objectType = gitObjectAlreadyFound.ObjectType; + return (int)ReturnCode.GIT_ENOTFOUND; + } - data = Allocate(gitObjectAlreadyFound.Data.LongLength); - data.Write(gitObjectAlreadyFound.Data, 0, gitObjectAlreadyFound.Data.Length); + int ret = Read(matchingKey, out data, out objectType); - return GIT_OK; + if (ret != (int)ReturnCode.GIT_OK) + { + return ret; } - return GIT_ENOTFOUND; + id = matchingKey; + + return (int)ReturnCode.GIT_OK; } - public override int Write(byte[] oid, Stream dataStream, long length, GitObjectType objectType, out byte[] finalOid) + public override int Write(ObjectId oid, Stream dataStream, long length, ObjectType objectType) { - using (var sha1 = new SHA1CryptoServiceProvider()) - { - finalOid = sha1.ComputeHash(dataStream); + var buffer = ReadBuffer(dataStream, length); - dataStream.Seek(0, SeekOrigin.Begin); - } + m_objectIdToContent.Add(oid, + new MockGitObject(oid, objectType, length, new List { buffer })); - if (m_objectIdToContent.ContainsKey(finalOid)) - { - return GIT_EEXISTS; - } + return (int)ReturnCode.GIT_OK; + } - if (length > (long)int.MaxValue) + private static byte[] ReadBuffer(Stream dataStream, long length) + { + if (length > int.MaxValue) { - return GIT_ERROR; + throw new InvalidOperationException( + string.Format("Provided length ({0}) exceeds int.MaxValue ({1}).", length, int.MaxValue)); } - byte[] buffer = new byte[length]; + var buffer = new byte[length]; int bytesRead = dataStream.Read(buffer, 0, (int)length); if (bytesRead != (int)length) { - return GIT_ERROR; + throw new InvalidOperationException( + string.Format("Too short buffer. {0} bytes were expected. {1} have been successfully read.", length, + bytesRead)); } - - m_objectIdToContent.Add(finalOid, new MockGitObject(finalOid, objectType, buffer)); - - return GIT_OK; + return buffer; } - public override int WriteStream(long length, GitObjectType objectType, out OdbBackendStream stream) + public override int WriteStream(long length, ObjectType objectType, out OdbBackendStream stream) { stream = new MockOdbBackendStream(this, objectType, length); - return GIT_OK; + return (int)ReturnCode.GIT_OK; } - public override bool Exists(byte[] oid) + public override bool Exists(ObjectId oid) { return m_objectIdToContent.ContainsKey(oid); } - private Dictionary m_objectIdToContent = new Dictionary(MockGitObjectComparer.Instance); + public override int ExistsPrefix(string shortSha, out ObjectId found) + { + found = null; + int numFound = 0; - private const int GIT_OK = 0; - private const int GIT_ERROR = -1; - private const int GIT_ENOTFOUND = -3; - private const int GIT_EEXISTS = -4; - private const int GIT_EAMBIGUOUS = -5; + foreach (ObjectId id in m_objectIdToContent.Keys) + { + if (!id.Sha.StartsWith(shortSha)) + { + continue; + } - #region Unimplemented + found = id; + numFound++; - public override int ReadHeader(byte[] oid, out int length, out GitObjectType objectType) + if (numFound > 1) + { + found = null; + return (int)ReturnCode.GIT_EAMBIGUOUS; + } + } + + if (numFound == 0) + { + found = null; + return (int)ReturnCode.GIT_ENOTFOUND; + } + + return (int)ReturnCode.GIT_OK; + } + + public override int ReadHeader(ObjectId oid, out int length, out ObjectType objectType) { - throw new NotImplementedException(); + objectType = default(ObjectType); + length = 0; + + MockGitObject gitObject; + + if (!m_objectIdToContent.TryGetValue(oid, out gitObject)) + { + return (int)ReturnCode.GIT_ENOTFOUND; + } + + objectType = gitObject.ObjectType; + length = (int)gitObject.Length; + + return (int)ReturnCode.GIT_OK; } - public override int ReadStream(byte[] oid, out OdbBackendStream stream) + private readonly Dictionary m_objectIdToContent = + new Dictionary(); + + private Action disposer; + + #region Unimplemented + + public override int ReadStream(ObjectId oid, out OdbBackendStream stream) { throw new NotImplementedException(); } public override int ForEach(ForEachCallback callback) { - throw new NotImplementedException(); + foreach (var objectId in m_objectIdToContent.Keys) + { + int result = callback(objectId); + + if (result != (int)ReturnCode.GIT_OK) + { + return result; + } + } + + return (int)ReturnCode.GIT_OK; } #endregion @@ -195,19 +475,11 @@ public override int ForEach(ForEachCallback callback) private class MockOdbBackendStream : OdbBackendStream { - public MockOdbBackendStream(MockOdbBackend backend, GitObjectType objectType, long length) + public MockOdbBackendStream(MockOdbBackend backend, ObjectType objectType, long length) : base(backend) { m_type = objectType; m_length = length; - m_hash = new SHA1CryptoServiceProvider(); - } - - protected override void Dispose() - { - ((IDisposable)m_hash).Dispose(); - - base.Dispose(); } public override bool CanRead @@ -228,68 +500,35 @@ public override bool CanWrite public override int Write(Stream dataStream, long length) { - if (null == m_buffer) - { - m_buffer = new byte[length]; - - if (length > (long)int.MaxValue) - return GIT_ERROR; - - int bytesRead = dataStream.Read(m_buffer, 0, (int)length); - - if (bytesRead != (int)length) - return GIT_ERROR; - - m_hash.TransformBlock(m_buffer, 0, (int)length, null, 0); - } - else - { - long newLength = m_buffer.LongLength + length; - - if (newLength > (long)int.MaxValue) - return GIT_ERROR; - - byte[] newBuffer = new byte[newLength]; - Array.Copy(m_buffer, newBuffer, m_buffer.Length); - - int bytesRead = dataStream.Read(newBuffer, m_buffer.Length, (int)length); - - if (bytesRead != (int)length) - return GIT_ERROR; + var buffer = ReadBuffer(dataStream, length); - m_hash.TransformBlock(newBuffer, m_buffer.Length, (int)length, null, 0); + m_chunks.Add(buffer); - m_buffer = newBuffer; - } - - return GIT_OK; + return (int)ReturnCode.GIT_OK; } - public override int FinalizeWrite(out byte[] oid) + public override int FinalizeWrite(ObjectId oid) { - m_hash.TransformFinalBlock(m_buffer, 0, 0); - oid = m_hash.Hash; + long totalLength = m_chunks.Sum(chunk => chunk.Length); - if (m_buffer.Length != (int)m_length) + if (totalLength != m_length) { - return GIT_ERROR; + throw new InvalidOperationException( + string.Format("Invalid final length. {0} was expected. The total size of the received chunks is {1}.", m_length, totalLength)); } var backend = (MockOdbBackend)Backend; - if (!backend.m_objectIdToContent.ContainsKey(oid)) - { - backend.m_objectIdToContent.Add(oid, new MockGitObject(oid, m_type, m_buffer)); - } + backend.m_objectIdToContent.Add(oid, + new MockGitObject(oid, m_type, m_length, m_chunks)); - return GIT_OK; + return (int)ReturnCode.GIT_OK; } - private byte[] m_buffer; + private readonly List m_chunks = new List(); - private readonly GitObjectType m_type; + private readonly ObjectType m_type; private readonly long m_length; - private readonly HashAlgorithm m_hash; #region Unimplemented @@ -307,79 +546,18 @@ public override int Read(Stream dataStream, long length) private class MockGitObject { - public MockGitObject(byte[] objectId, GitObjectType objectType, byte[] data) + public MockGitObject(ObjectId objectId, ObjectType objectType, long length, List data) { - if (objectId.Length != 20) - { - throw new InvalidOperationException(); - } - - this.ObjectId = objectId; - this.ObjectType = objectType; - this.Data = data; - } - - public byte[] ObjectId; - public GitObjectType ObjectType; - public byte[] Data; - - public int Length - { - get - { - return this.Data.Length; - } - } - } - - #endregion - - #region MockGitObjectComparer - - private class MockGitObjectComparer : IEqualityComparer - { - public bool Equals(byte[] x, byte[] y) - { - for (int i = 0; i < 20; i++) - { - if (x[i] != y[i]) - { - return false; - } - } - - return true; - } - - public int GetHashCode(byte[] obj) - { - int toReturn = 0; - - for (int i = 0; i < obj.Length / 4; i++) - { - toReturn ^= (int)obj[4 * i] << 24 + - (int)obj[4 * i + 1] << 16 + - (int)obj[4 * i + 2] << 8 + - (int)obj[4 * i + 3]; - } - - return toReturn; - } - - public static MockGitObjectComparer Instance - { - get - { - if (null == s_instance) - { - s_instance = new MockGitObjectComparer(); - } - - return s_instance; - } + ObjectId = objectId; + ObjectType = objectType; + Data = data; + Length = length; } - private static MockGitObjectComparer s_instance; + public readonly ObjectId ObjectId; + public readonly ObjectType ObjectType; + public readonly List Data; + public readonly long Length; } #endregion 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 new file mode 100644 index 000000000..758a08e2a --- /dev/null +++ b/LibGit2Sharp.Tests/PatchStatsFixture.cs @@ -0,0 +1,28 @@ +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class PatchStatsFixture : BaseFixture + { + [Fact] + public void CanExtractStatisticsFromDiff() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var oldTree = repo.Lookup("origin/packed-test").Tree; + var newTree = repo.Lookup("HEAD").Tree; + using (var stats = repo.Diff.Compare(oldTree, newTree)) + { + 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); + } + } + } + } +} 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 new file mode 100644 index 000000000..824c1d8c0 --- /dev/null +++ b/LibGit2Sharp.Tests/PushFixture.cs @@ -0,0 +1,262 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using LibGit2Sharp.Handlers; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class PushFixture : BaseFixture + { + private void OnPushStatusError(PushStatusError pushStatusErrors) + { + Assert.Fail(string.Format("Failed to update reference '{0}': {1}", pushStatusErrors.Reference, pushStatusErrors.Message)); + } + + private void AssertPush(Action push) + { + var scd = BuildSelfCleaningDirectory(); + + string originalRepoPath = SandboxBareTestRepo(); + string clonedRepoPath = Repository.Clone(originalRepoPath, scd.DirectoryPath); + + using (var originalRepo = new Repository(originalRepoPath)) + using (var clonedRepo = new Repository(clonedRepoPath)) + { + Remote remote = clonedRepo.Network.Remotes["origin"]; + + // Compare before + Assert.Equal(originalRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier, + clonedRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier); + Assert.Equal( + clonedRepo.Network.ListReferences(remote).Single(r => r.CanonicalName == "refs/heads/master"), + clonedRepo.Refs.Head.ResolveToDirectReference()); + + // Change local state (commit) + const string relativeFilepath = "new_file.txt"; + Touch(clonedRepo.Info.WorkingDirectory, relativeFilepath, "__content__"); + Commands.Stage(clonedRepo, relativeFilepath); + clonedRepo.Commit("__commit_message__", Constants.Signature, Constants.Signature); + + // Assert local state has changed + Assert.NotEqual(originalRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier, + clonedRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier); + Assert.NotEqual( + clonedRepo.Network.ListReferences(remote).Single(r => r.CanonicalName == "refs/heads/master"), + clonedRepo.Refs.Head.ResolveToDirectReference()); + + // Push the change upstream (remote state is supposed to change) + push(clonedRepo); + + // Assert that both local and remote repos are in sync + Assert.Equal(originalRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier, + clonedRepo.Refs["HEAD"].ResolveToDirectReference().TargetIdentifier); + Assert.Equal( + clonedRepo.Network.ListReferences(remote).Single(r => r.CanonicalName == "refs/heads/master"), + clonedRepo.Refs.Head.ResolveToDirectReference()); + } + } + + [Fact] + public void CanPushABranchTrackingAnUpstreamBranch() + { + bool packBuilderCalled = false; + PackBuilderProgressHandler packBuilderCb = (x, y, z) => { packBuilderCalled = 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, + }; + + AssertPush(repo => repo.Network.Push(repo.Network.Remotes["origin"], "HEAD", @"refs/heads/master", options)); + 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() + { + Assert.Throws( + () => + AssertPush(repo => + { + Branch branch = repo.Branches["master"]; + repo.Branches.Update(branch, b => b.TrackedBranch = null); + repo.Network.Push(branch); + })); + } + + [Fact] + public void CanForcePush() + { + string remoteRepoPath = InitNewRepository(true); + + // Create a new repository + string localRepoPath = InitNewRepository(); + using (var localRepo = new Repository(localRepoPath, new RepositoryOptions { Identity = Constants.Identity })) + { + // Add a commit + Commit first = AddCommitToRepo(localRepo); + + Remote remote = localRepo.Network.Remotes.Add("origin", remoteRepoPath); + + localRepo.Branches.Update(localRepo.Head, + b => b.Remote = remote.Name, + b => b.UpstreamBranch = localRepo.Head.CanonicalName); + + // Push this commit + localRepo.Network.Push(localRepo.Head); + AssertRemoteHeadTipEquals(localRepo, first.Sha); + + UpdateTheRemoteRepositoryWithANewCommit(remoteRepoPath); + + // Add another commit + var oldId = localRepo.Head.Tip.Id; + Commit second = AddCommitToRepo(localRepo); + + // Try to fast forward push this new commit + Assert.Throws(() => localRepo.Network.Push(localRepo.Head)); + + // 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", + "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()); + Reference remoteHead = remoteReferences.Single(r => r.CanonicalName == "HEAD"); + + Assert.Equal(sha, remoteHead.ResolveToDirectReference().TargetIdentifier); + } + + private void UpdateTheRemoteRepositoryWithANewCommit(string remoteRepoPath) + { + // Perform a fresh clone of the upstream repository + var scd = BuildSelfCleaningDirectory(); + string clonedRepoPath = Repository.Clone(remoteRepoPath, scd.DirectoryPath); + + using (var clonedRepo = new Repository(clonedRepoPath)) + { + // Add a commit + AddCommitToRepo(clonedRepo); + + // Push this new commit toward an upstream repository + clonedRepo.Network.Push(clonedRepo.Head); + } + } + + private Commit AddCommitToRepo(IRepository repository) + { + + string random = Path.GetRandomFileName(); + string filename = random + ".txt"; + + Touch(repository.Info.WorkingDirectory, filename, random); + + 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 new file mode 100644 index 000000000..e0639caa8 --- /dev/null +++ b/LibGit2Sharp.Tests/RefSpecFixture.cs @@ -0,0 +1,243 @@ +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class RefSpecFixture : BaseFixture + { + [Fact] + public void CanCountRefSpecs() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes["origin"]; + Assert.Single(remote.RefSpecs); + } + } + + [Fact] + public void CanIterateOverRefSpecs() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes["origin"]; + int count = 0; + foreach (RefSpec refSpec in remote.RefSpecs) + { + Assert.NotNull(refSpec); + count++; + } + Assert.Equal(1, count); + } + } + + [Fact] + public void FetchAndPushRefSpecsComposeRefSpecs() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes["origin"]; + + var totalRefSpecs = remote.FetchRefSpecs.Concat(remote.PushRefSpecs); + var orderedRefSpecs = remote.RefSpecs.OrderBy(r => r.Direction == RefSpecDirection.Fetch ? 0 : 1); + Assert.Equal(orderedRefSpecs, totalRefSpecs); + } + } + + [Fact] + public void CanReadRefSpecDetails() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes["origin"]; + + RefSpec refSpec = remote.RefSpecs.First(); + Assert.NotNull(refSpec); + + Assert.Equal("refs/heads/*", refSpec.Source); + Assert.Equal("refs/remotes/origin/*", refSpec.Destination); + Assert.True(refSpec.ForceUpdate); + } + } + + [Theory] + [InlineData(new string[] { "+refs/tags/*:refs/tags/*" }, new string[] { "refs/heads/*:refs/remotes/test/*", "+refs/abc:refs/def" })] + [InlineData(new string[] { "+refs/abc/x:refs/def/x", "refs/def:refs/ghi" }, new string[0])] + [InlineData(new string[0], new string[] { "refs/ghi:refs/jkl/mno" })] + public void CanReplaceRefSpecs(string[] newFetchRefSpecs, string[] newPushRefSpecs) + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + List oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.ToList(); + + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs = newFetchRefSpecs, r => r.PushRefSpecs = newPushRefSpecs); + Assert.Equal(oldRefSpecs, remote.RefSpecs.ToList()); + } + + 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); + } + } + } + + [Fact] + public void RemoteUpdaterSavesRefSpecsPermanently() + { + var fetchRefSpecs = new string[] { "refs/their/heads/*:refs/my/heads/*", "+refs/their/tag:refs/my/tag" }; + var path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Update("origin", r => r.FetchRefSpecs = fetchRefSpecs); + } + + using (var repo = new Repository(path)) + using (var remote = repo.Network.Remotes["origin"]) + { + var actualRefSpecs = remote.RefSpecs + .Where(r => r.Direction == RefSpecDirection.Fetch) + .Select(r => r.Specification) + .ToArray(); + Assert.Equal(fetchRefSpecs, actualRefSpecs); + } + } + + [Fact] + public void CanAddAndRemoveRefSpecs() + { + string newRefSpec = "+refs/heads/test:refs/heads/other-test"; + var path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs.Add(newRefSpec), + r => r.PushRefSpecs.Add(newRefSpec)); + + 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)); + } + + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs.Remove(newRefSpec), + r => r.PushRefSpecs.Remove(newRefSpec)); + + 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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + + // Push refspec does not exist in cloned repository + repo.Network.Remotes.Update("origin", r => r.PushRefSpecs.Add("+refs/test:refs/test")); + + repo.Network.Remotes.Update("origin", + r => r.FetchRefSpecs.Clear(), + r => r.PushRefSpecs.Clear()); + + using (var remote = repo.Network.Remotes["origin"]) + { + Assert.Empty(remote.FetchRefSpecs); + Assert.Empty(remote.PushRefSpecs); + Assert.Empty(remote.RefSpecs); + } + } + } + + [Theory] + [InlineData("refs/test:refs//double-slash")] + [InlineData("refs/trailing-slash/:refs/test")] + [InlineData("refs/.dotfile:refs/test")] + [InlineData("refs/.:refs/dotdir")] + [InlineData("refs/asterix:refs/not-matching/*")] + [InlineData("refs/double/*/asterix/*:refs/double/*asterix/*")] + [InlineData("refs/ whitespace:refs/test")] + public void SettingInvalidRefSpecsThrows(string refSpec) + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + IEnumerable oldRefSpecs; + using (var remote = repo.Network.Remotes["origin"]) + { + oldRefSpecs = remote.RefSpecs.Select(r => r.Specification).ToList(); + } + + 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 5f8f0eb3e..b4ec734d5 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -12,8 +12,8 @@ public class ReferenceFixture : BaseFixture private readonly string[] expectedRefs = new[] { "refs/heads/br2", "refs/heads/deadbeef", "refs/heads/master", "refs/heads/packed", "refs/heads/packed-test", - "refs/heads/test", "refs/notes/answer", "refs/notes/answer2", "refs/notes/commits", "refs/tags/e90810b", - "refs/tags/lw", "refs/tags/point_to_blob", "refs/tags/test" + "refs/heads/test", "refs/notes/answer", "refs/notes/answer2", "refs/notes/commits", "refs/tags/e90810b", + "refs/tags/lw", "refs/tags/point_to_blob", "refs/tags/tag_without_tagger", "refs/tags/test" }; [Fact] @@ -21,9 +21,13 @@ public void CanAddADirectReference() { const string name = "refs/heads/unit_test"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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); @@ -31,6 +35,11 @@ public void CanAddADirectReference() Assert.Equal("be3563ae3f795b2b4353bcce3a527ad0a4f7f644", newRef.Target.Sha); Assert.Equal(newRef.Target.Sha, newRef.TargetIdentifier); Assert.NotNull(repo.Refs[name]); + + AssertRefLogEntry(repo, name, + "branch: Created from be3563ae3f795b2b4353bcce3a527ad0a4f7f644", + null, newRef.ResolveToDirectReference().Target.Id, Constants.Identity, before + ); } } @@ -38,17 +47,26 @@ public void CanAddADirectReference() public void CanAddADirectReferenceFromRevParseSpec() { const string name = "refs/heads/extendedShaSyntaxRulz"; + const string logMessage = "Create new ref"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { - var newRef = (DirectReference)repo.Refs.Add(name, "master^1^2"); + EnableRefLog(repo); + + 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); Assert.Equal("c47800c7266a2be04c571c04d5a6614691ea99bd", newRef.Target.Sha); Assert.Equal(newRef.Target.Sha, newRef.TargetIdentifier); Assert.NotNull(repo.Refs[name]); + + AssertRefLogEntry(repo, name, logMessage, + null, newRef.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } @@ -57,10 +75,10 @@ public void CreatingADirectReferenceWithARevparseSpecPointingAtAnUnknownObjectFa { const string name = "refs/heads/extendedShaSyntaxRulz"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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")); } } @@ -70,8 +88,8 @@ public void CanAddASymbolicReferenceFromTheTargetName() const string name = "refs/heads/unit_test"; const string target = "refs/heads/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, target); @@ -84,19 +102,23 @@ public void CanAddASymbolicReferenceFromTheTargetReference() { const string name = "refs/heads/unit_test"; const string target = "refs/heads/master"; + const string logMessage = "unit_test reference init"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { + EnableRefLog(repo); + var targetRef = repo.Refs[target]; - var newRef = repo.Refs.Add(name, targetRef); + var newRef = repo.Refs.Add(name, targetRef, logMessage); AssertSymbolicRef(newRef, repo, target, name); + Assert.Empty(repo.Refs.Log(newRef)); } } - private static void AssertSymbolicRef(SymbolicReference newRef, Repository repo, string expectedTargetName, string expectedName) + private static void AssertSymbolicRef(SymbolicReference newRef, IRepository repo, string expectedTargetName, string expectedName) { Assert.NotNull(newRef); Assert.Equal(expectedName, newRef.CanonicalName); @@ -109,8 +131,8 @@ private static void AssertSymbolicRef(SymbolicReference newRef, Repository repo, [Fact] public void BlindlyCreatingADirectReferenceOverAnExistingOneThrows() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("refs/heads/master", "be3563ae3f795b2b4353bcce3a527ad0a4f7f644")); } @@ -119,8 +141,8 @@ public void BlindlyCreatingADirectReferenceOverAnExistingOneThrows() [Fact] public void BlindlyCreatingASymbolicReferenceOverAnExistingOneThrows() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Refs.Add("HEAD", "refs/heads/br2")); } @@ -131,16 +153,28 @@ public void CanAddAndOverwriteADirectReference() { const string name = "refs/heads/br2"; const string target = "4c062a6361ae6959e06292c1fa5e2822d9c96345"; + const string logMessage = "Create new ref"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { - var newRef = (DirectReference)repo.Refs.Add(name, target, true); + EnableRefLog(repo); + + var oldRef = repo.Refs[name]; + + 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); Assert.Equal(target, newRef.Target.Sha); Assert.Equal(target, ((DirectReference)repo.Refs[name]).Target.Sha); + + AssertRefLogEntry(repo, name, + logMessage, ((DirectReference)oldRef).Target.Id, + newRef.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } @@ -149,23 +183,36 @@ public void CanAddAndOverwriteASymbolicReference() { const string name = "HEAD"; const string target = "refs/heads/br2"; + const string logMessage = "Create new ref"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, new RepositoryOptions { Identity = Constants.Identity })) { - var newRef = (SymbolicReference)repo.Refs.Add(name, target, true); + EnableRefLog(repo); + + var oldtarget = repo.Refs[name].ResolveToDirectReference().Target.Id; + + 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, logMessage, + oldtarget, + newRef.ResolveToDirectReference().Target.Id, + 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)); } @@ -174,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")); } @@ -183,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)); @@ -193,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")); } @@ -202,8 +252,8 @@ public void AddWithNullStringThrows() [Fact] public void CanRemoveAReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { repo.Refs.Remove("refs/heads/packed"); } @@ -212,8 +262,8 @@ public void CanRemoveAReference() [Fact] public void CanRemoveANonExistingReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string unknown = "refs/heads/dahlbyk/has/hawkeyes"; @@ -226,8 +276,8 @@ public void CanRemoveANonExistingReference() [Fact] public void ARemovedReferenceCannotBeLookedUp() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string refName = "refs/heads/test"; @@ -239,18 +289,18 @@ public void ARemovedReferenceCannotBeLookedUp() [Fact] public void RemovingAReferenceDecreasesTheRefsCount() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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); } @@ -259,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)); } @@ -268,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)); @@ -278,21 +330,22 @@ public void RemoveWithNullNameThrows() [Fact] public void CanListAllReferencesEvenCorruptedOnes() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { CreateCorruptedDeadBeefHead(repo.Info.Path); - Assert.Equal(expectedRefs, repo.Refs.Select(r => r.CanonicalName).ToArray()); + Assert.Equal(expectedRefs, SortedRefs(repo, r => r.CanonicalName)); - Assert.Equal(13, repo.Refs.Count()); + Assert.Equal(14, repo.Refs.Count()); } } [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); @@ -313,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); @@ -327,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); @@ -341,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); @@ -355,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]; }); } @@ -364,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]; }); } @@ -374,8 +432,8 @@ public void ResolvingWithNullThrows() public void CanUpdateTargetOfADirectReference() { const string masterRef = "refs/heads/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { string sha = repo.Refs["refs/heads/test"].ResolveToDirectReference().Target.Sha; Reference master = repo.Refs[masterRef]; @@ -394,14 +452,14 @@ public void CanUpdateTargetOfADirectReference() public void CanUpdateTargetOfADirectReferenceWithAnAbbreviatedSha() { const string masterRef = "refs/heads/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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); @@ -414,8 +472,8 @@ public void CanUpdateTargetOfADirectReferenceWithAnAbbreviatedSha() public void CanUpdateTargetOfASymbolicReference() { const string name = "refs/heads/unit_test"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, "refs/heads/master"); Assert.NotNull(newRef); @@ -432,8 +490,8 @@ public void CanUpdateTargetOfASymbolicReference() [Fact] public void CanUpdateHeadWithARevparseSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch test = repo.Branches["test"]; @@ -450,41 +508,74 @@ public void CanUpdateHeadWithARevparseSpec() [Fact] public void CanUpdateHeadWithEitherAnObjectIdOrAReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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)); + 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); - Reference symref = repo.Refs.UpdateTarget(head, test); + var testTargetId = test.ResolveToDirectReference().Target.Id; + AssertRefLogEntry(repo, "HEAD", null, + head.ResolveToDirectReference().Target.Id, + testTargetId, + Constants.Identity, before); + + const string secondLogMessage = "second update target message"; + + 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", + secondLogMessage, + testTargetId, + testTargetId, + Constants.Identity, before); } } [Fact] public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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 before = DateTimeOffset.Now.TruncateMilliseconds(); - var newRef = (DirectReference)repo.Refs.UpdateTarget(master, "master^1^2"); + var newRef = (DirectReference)repo.Refs.UpdateTarget(master, "master^1^2", logMessage); Assert.NotNull(newRef); Assert.Equal(name, newRef.CanonicalName); Assert.NotNull(newRef.Target); Assert.Equal("c47800c7266a2be04c571c04d5a6614691ea99bd", newRef.Target.Sha); Assert.Equal(newRef.Target.Sha, newRef.TargetIdentifier); Assert.NotNull(repo.Refs[name]); + + AssertRefLogEntry(repo, name, + logMessage, + @from, + newRef.Target.Id, + Constants.Identity, before); } } @@ -492,8 +583,8 @@ public void CanUpdateTargetOfADirectReferenceWithARevparseSpec() public void UpdatingADirectRefWithSymbolFails() { const string name = "refs/heads/unit_test"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var newRef = (SymbolicReference)repo.Refs.Add(name, "refs/heads/master"); Assert.NotNull(newRef); @@ -509,8 +600,8 @@ public void UpdatingADirectRefWithSymbolFails() public void CanUpdateTargetOfADirectReferenceWithAShortReferenceNameAsARevparseSpec() { const string masterRef = "refs/heads/master"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Reference updatedMaster = repo.Refs.UpdateTarget(masterRef, "heads/test"); Assert.Equal(repo.Refs["refs/heads/test"].TargetIdentifier, updatedMaster.TargetIdentifier); @@ -520,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)); @@ -533,138 +625,156 @@ 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 CanMoveAReferenceToADeeperReferenceHierarchy() + public void CanRenameAReferenceToADeeperReferenceHierarchy() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string newName = "refs/tags/test/deep"; - Reference moved = repo.Refs.Move("refs/tags/test", newName); - Assert.NotNull(moved); - Assert.Equal(newName, moved.CanonicalName); + Reference renamed = repo.Refs.Rename("refs/tags/test", newName); + Assert.NotNull(renamed); + Assert.Equal(newName, renamed.CanonicalName); } } [Fact] - public void CanMoveAReferenceToAUpperReferenceHierarchy() + public void CanRenameAReferenceToAUpperReferenceHierarchy() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string newName = "refs/heads/o/sole"; const string oldName = newName + "/mio"; repo.Refs.Add(oldName, repo.Head.CanonicalName); - Reference moved = repo.Refs.Move(oldName, newName); - Assert.NotNull(moved); - Assert.Equal(newName, moved.CanonicalName); + Reference renamed = repo.Refs.Rename(oldName, newName); + Assert.NotNull(renamed); + Assert.Equal(newName, renamed.CanonicalName); } } [Fact] - public void CanMoveAReferenceToADifferentReferenceHierarchy() + public void CanRenameAReferenceToADifferentReferenceHierarchy() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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"; - Reference moved = repo.Refs.Move("refs/tags/test", newName); - Assert.NotNull(moved); - Assert.Equal(newName, moved.CanonicalName); + EnableRefLog(repo); + + 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, + string.Format("reference: renamed {0} to {1}", oldName, newName), + oldId, + renamed.ResolveToDirectReference().Target.Id, + Constants.Identity, before); } } [Fact] - public void MovingANonExistingReferenceThrows() + public void RenamingANonExistingReferenceThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Refs.Move("refs/tags/i-am-void", "refs/atic/tagtest")); + Assert.Throws(() => repo.Refs.Rename("refs/tags/i-am-void", "refs/atic/tagtest")); } } [Fact] - public void CanMoveAndOverWriteAExistingReference() + public void CanRenameAndOverWriteAExistingReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string oldName = "refs/heads/packed"; const string newName = "refs/heads/br2"; - Reference moved = repo.Refs.Move(oldName, newName, true); + Reference renamed = repo.Refs.Rename(oldName, newName, allowOverwrite: true); Assert.Null(repo.Refs[oldName]); - Assert.NotNull(repo.Refs[moved.CanonicalName]); + Assert.NotNull(repo.Refs[renamed.CanonicalName]); } } [Fact] public void BlindlyOverwritingAExistingReferenceThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Refs.Move("refs/heads/packed", "refs/heads/br2")); + Assert.Throws(() => repo.Refs.Rename("refs/heads/packed", "refs/heads/br2")); } } [Fact] - public void MovingAReferenceDoesNotDecreaseTheRefsCount() + public void RenamingAReferenceDoesNotDecreaseTheRefsCount() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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.Move(oldName, newName); + 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); } } [Fact] - public void CanLookupAMovedReference() + public void CanLookupARenamedReference() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string oldName = "refs/tags/test"; const string newName = "refs/atic/tagtest"; - Reference moved = repo.Refs.Move(oldName, newName); + Reference renamed = repo.Refs.Rename(oldName, newName); Reference lookedUp = repo.Refs[newName]; - Assert.Equal(lookedUp, moved); + Assert.Equal(lookedUp, renamed); } } [Fact] public void CanFilterReferencesWithAGlob() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(12, repo.Refs.FromGlob("*").Count()); + Assert.Equal(13, repo.Refs.FromGlob("*").Count()); Assert.Equal(5, repo.Refs.FromGlob("refs/heads/*").Count()); - Assert.Equal(4, repo.Refs.FromGlob("refs/tags/*").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")); } } @@ -680,9 +790,111 @@ public void CanFilterReferencesWithAGlob() [InlineData("/", false)] public void CanTellIfAReferenceIsValid(string refname, bool expectedResult) { - using (var repo = new Repository(BareTestRepoPath)) + Assert.Equal(expectedResult, Reference.IsValidName(refname)); + } + + [Fact] + public void CanUpdateTheTargetOfASymbolicReferenceWithAnotherSymbolicReference() + { + string repoPath = SandboxBareTestRepo(); + using (var repo = new Repository(repoPath)) + { + Reference symbolicRef = repo.Refs.Add("refs/heads/unit_test", "refs/heads/master"); + + Reference newHead = repo.Refs.UpdateTarget(repo.Refs.Head, symbolicRef); + var symbolicHead = Assert.IsType(newHead); + Assert.Equal(symbolicRef.CanonicalName, newHead.TargetIdentifier); + Assert.Equal(symbolicRef, symbolicHead.Target); + } + } + + [Fact] + public void LookingForLowerCaseHeadThrows() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Refs["head"]); + } + } + + private static T[] SortedRefs(IRepository repo, Func selector) + { + return repo.Refs.OrderBy(r => r.CanonicalName, StringComparer.Ordinal).Select(selector).ToArray(); + } + + [Fact] + public void CanIdentifyReferenceKind() + { + 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); + } + + path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.True(repo.Refs["refs/notes/commits"].IsNote); + } + } + + [Fact] + public void CanQueryReachability() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var result = repo.Refs.ReachableFrom( + new[] { repo.Lookup("f8d44d7"), repo.Lookup("6dcf9bf") }); + + var expected = new[] + { + "refs/heads/diff-test-cases", + "refs/heads/i-do-numbers", + "refs/remotes/origin/test", + "refs/tags/e90810b", + "refs/tags/lw", + "refs/tags/test", + }; + + Assert.Equal(expected, result.Select(x => x.CanonicalName).OrderBy(x => x).ToList()); + } + } + + [Fact] + public void CanQueryReachabilityAmongASubsetOfreferences() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var result = repo.Refs.ReachableFrom( + repo.Refs.Where(r => r.IsTag), + new[] { repo.Lookup("f8d44d7"), repo.Lookup("6dcf9bf") }); + + var expected = new[] + { + "refs/tags/e90810b", + "refs/tags/lw", + "refs/tags/test", + }; + + Assert.Equal(expected, result.Select(x => x.CanonicalName).OrderBy(x => x).ToList()); + } + } + + [Fact] + public void CanHandleInvalidArguments() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Assert.Equal(expectedResult, repo.Refs.IsValidName(refname)); + 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(Array.Empty())); } } } diff --git a/LibGit2Sharp.Tests/ReflogFixture.cs b/LibGit2Sharp.Tests/ReflogFixture.cs new file mode 100644 index 000000000..52973454b --- /dev/null +++ b/LibGit2Sharp.Tests/ReflogFixture.cs @@ -0,0 +1,232 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class ReflogFixture : BaseFixture + { + [Fact] + public void CanReadReflog() + { + const int expectedReflogEntriesCount = 3; + + 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().Committer.Email); + Assert.StartsWith("clone: from", reflog.Last().Message); + Assert.Equal(ObjectId.Zero, reflog.Last().From); + + // second commit assertions + Assert.Equal("4c062a6361ae6959e06292c1fa5e2822d9c96345", reflog.ElementAt(expectedReflogEntriesCount - 2).From.Sha); + Assert.Equal("592d3c869dbc4127fc57c189cb94f2794fa84e7e", reflog.ElementAt(expectedReflogEntriesCount - 2).To.Sha); + } + } + + [Fact] + public void ReflogOfUnbornReferenceIsEmpty() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Empty(repo.Refs.Log("refs/heads/toto")); + } + } + + [Fact] + public void ReadingReflogOfInvalidReferenceNameThrows() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Refs.Log("toto").Count()); + } + } + + [Fact] + public void CommitShouldCreateReflogEntryOnHeadAndOnTargetedDirectReference() + { + string repoPath = InitNewRepository(); + + 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"); + Assert.NotNull(newRef); + repo.Refs.UpdateTarget(repo.Refs.Head, newRef); + + const string relativeFilepath = "new.txt"; + Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); + 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.Single(repo.Refs.Log("HEAD")); + var reflogEntry = repo.Refs.Log("HEAD").First(); + + 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.Single(repo.Refs.Log("refs/heads/master")); + reflogEntry = repo.Refs.Log("HEAD").First(); + + 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.Empty(repo.Refs.Log("refs/heads/unit_test")); + } + } + + [Fact] + public void CommitOnUnbornReferenceShouldCreateReflogEntryWithInitialTag() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + const string relativeFilepath = "new.txt"; + Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); + 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.Single(repo.Refs.Log("HEAD")); + Assert.Equal(string.Format("commit (initial): {0}", commitMessage), repo.Refs.Log("HEAD").First().Message); + } + } + + [Fact] + public void CommitOnDetachedHeadShouldInsertReflogEntry() + { + string repoPath = SandboxStandardTestRepo(); + + 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(); + Commands.Checkout(repo, parentCommit.Sha); + Assert.True(repo.Info.IsHeadDetached); + + const string relativeFilepath = "new.txt"; + Touch(repo.Info.WorkingDirectory, relativeFilepath, "content\n"); + 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(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); + } + } + + [Theory] + [InlineData(false, null, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + [InlineData(true, null, false)] + [InlineData(true, false, false)] + [InlineData(false, false, false)] + public void AppendingToReflogDependsOnCoreLogAllRefUpdatesSetting(bool isBare, bool? setting, bool expectAppend) + { + var repoPath = InitNewRepository(isBare); + + using (var repo = new Repository(repoPath)) + { + if (setting != null) + { + EnableRefLog(repo, setting.Value); + } + + var blob = repo.ObjectDatabase.CreateBlob(Stream.Null); + var tree = repo.ObjectDatabase.CreateTree(new TreeDefinition().Add("yoink", blob, Mode.NonExecutableFile)); + var commit = repo.ObjectDatabase.CreateCommit(Constants.Signature, Constants.Signature, "yoink", + tree, Enumerable.Empty(), false); + + var branch = repo.CreateBranch("yoink", commit); + var log = repo.Refs.Log(branch.CanonicalName); + + Assert.Equal(expectAppend ? 1 : 0, log.Count()); + } + } + + [Fact] + public void UnsignedMethodsWriteCorrectlyToTheReflog() + { + var repoPath = InitNewRepository(true); + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) + { + EnableRefLog(repo); + + var blob = repo.ObjectDatabase.CreateBlob(Stream.Null); + var tree = repo.ObjectDatabase.CreateTree(new TreeDefinition().Add("yoink", blob, Mode.NonExecutableFile)); + 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, 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 8c0a57e38..36990bb6e 100644 --- a/LibGit2Sharp.Tests/RemoteFixture.cs +++ b/LibGit2Sharp.Tests/RemoteFixture.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -10,10 +10,12 @@ 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); + AssertBelongsToARepository(repo, origin); Assert.Equal("origin", origin.Name); Assert.Equal("c:/GitHub/libgit2sharp/Resources/testrepo.git", origin.Url); } @@ -22,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"]); } @@ -31,149 +34,107 @@ 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; foreach (Remote remote in repo.Network.Remotes) { Assert.NotNull(remote); + AssertBelongsToARepository(repo, remote); count++; } - Assert.Equal(1, count); + Assert.Equal(2, count); } } - [Fact] - public void CanCheckEqualityOfRemote() + [Theory] + [InlineData(TagFetchMode.All)] + [InlineData(TagFetchMode.Auto)] + [InlineData(TagFetchMode.None)] + public void CanSetTagFetchMode(TagFetchMode tagFetchMode) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Remote oneOrigin = repo.Network.Remotes["origin"]; - Assert.NotNull(oneOrigin); - - Remote otherOrigin = repo.Network.Remotes["origin"]; - Assert.Equal(oneOrigin, otherOrigin); + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; - Remote createdRemote = repo.Network.Remotes.Add("origin2", oneOrigin.Url); + repo.Network.Remotes.Add(name, url); + Remote remote = repo.Network.Remotes[name]; + Assert.NotNull(remote); - Remote loadedRemote = repo.Network.Remotes["origin2"]; - Assert.NotNull(loadedRemote); - Assert.Equal(createdRemote, loadedRemote); + repo.Network.Remotes.Update(name, + r => r.TagFetchMode = tagFetchMode); - Assert.NotEqual(oneOrigin, loadedRemote); + using (var updatedremote = repo.Network.Remotes[name]) + { + Assert.Equal(tagFetchMode, updatedremote.TagFetchMode); + } } } - [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) + [Fact] + public void CanSetRemoteUrl() { - string remoteName = "testRemote"; - - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; + const string newUrl = "https://github.com/libgit2/libgit2.git"; - // Set up structures for the expected results - // and verifying the RemoteUpdateTips callback. - TestRemoteInfo expectedResults = TestRemoteInfo.TestRemoteInstance; - ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName); + repo.Network.Remotes.Add(name, url); + Remote remote = repo.Network.Remotes[name]; + Assert.NotNull(remote); - // Add expected branch objects - foreach (KeyValuePair kvp in expectedResults.BranchTips) - { - expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value); - } + repo.Network.Remotes.Update(name, r => r.Url = newUrl); - // Add the expected tags - string[] expectedTagNames = { "blob", "commit_tree", "annotated_tag" }; - foreach (string tagName in expectedTagNames) + using (var updatedremote = repo.Network.Remotes[name]) { - TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName]; - expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo); + Assert.Equal(newUrl, updatedremote.Url); + // with no push url set, PushUrl defaults to the fetch url + Assert.Equal(newUrl, updatedremote.PushUrl); } - - // Perform the actual fetch - remote.Fetch(onUpdateTips: expectedFetchState.RemoteUpdateTipsHandler); - - // Verify the expected - expectedFetchState.CheckUpdatedReferences(repo); } } - [SkippableFact] - public void CanFetchIntoAnEmptyRepositoryWithCredentials() + [Fact] + public void CanSetRemotePushUrl() { - InconclusiveIf(() => string.IsNullOrEmpty(Constants.PrivateRepoUrl), - "Populate Constants.PrivateRepo* to run this test"); - - string remoteName = "testRemote"; - - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Remote remote = repo.Network.Remotes.Add(remoteName, Constants.PrivateRepoUrl); - - // Perform the actual fetch - remote.Fetch(credentials: new Credentials - { - Username = Constants.PrivateRepoUsername, - Password = Constants.PrivateRepoPassword - }); - } - } - - [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) - { - string remoteName = "testRemote"; + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; + const string pushurl = "https://github.com/libgit2/libgit2.git"; - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) - { - Remote remote = repo.Network.Remotes.Add(remoteName, url); + repo.Network.Remotes.Add(name, url); + Remote remote = repo.Network.Remotes[name]; + Assert.NotNull(remote); - // Set up structures for the expected results - // and verifying the RemoteUpdateTips callback. - TestRemoteInfo remoteInfo = TestRemoteInfo.TestRemoteInstance; - ExpectedFetchState expectedFetchState = new ExpectedFetchState(remoteName); + // before setting push, both push and fetch urls should match + Assert.Equal(url, remote.Url); + Assert.Equal(url, remote.PushUrl); - // Add expected branch objects - foreach (KeyValuePair kvp in remoteInfo.BranchTips) - { - expectedFetchState.AddExpectedBranch(kvp.Key, ObjectId.Zero, kvp.Value); - } + repo.Network.Remotes.Update(name, r => r.PushUrl = pushurl); - // Add expected tags - foreach (KeyValuePair kvp in remoteInfo.Tags) + using (var updatedremote = repo.Network.Remotes[name]) { - expectedFetchState.AddExpectedTag(kvp.Key, ObjectId.Zero, kvp.Value); + // url should not change, push url should be set to new value + Assert.Equal(url, updatedremote.Url); + Assert.Equal(pushurl, updatedremote.PushUrl); } - - // Perform the actual fetch - remote.Fetch(TagFetchMode.All, onUpdateTips: expectedFetchState.RemoteUpdateTipsHandler); - - // Verify the expected - expectedFetchState.CheckUpdatedReferences(repo); } } [Fact] public void CreatingANewRemoteAddsADefaultRefSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { const string name = "upstream"; const string url = "https://github.com/libgit2/libgit2sharp.git"; @@ -195,9 +156,8 @@ public void CreatingANewRemoteAddsADefaultRefSpec() [Fact] public void CanAddANewRemoteWithAFetchRefSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { const string name = "pull-requests"; const string url = "https://github.com/libgit2/libgit2sharp.git"; @@ -217,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"; @@ -231,10 +192,197 @@ public void AddingARemoteWithAnInvalidNameThrows(string name) [InlineData("/", false)] public void CanTellIfARemoteNameIsValid(string refname, bool expectedResult) { - using (var repo = new Repository(BareTestRepoPath)) + Assert.Equal(expectedResult, Remote.IsValidName(refname)); + } + + [Fact] + public void DoesNotThrowWhenARemoteHasNoUrlSet() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + var noUrlRemote = repo.Network.Remotes["no_url"]; + Assert.NotNull(noUrlRemote); + Assert.Null(noUrlRemote.Url); + + var remotes = repo.Network.Remotes.ToList(); + Assert.Equal(1, remotes.Count(r => r.Name == "no_url")); + } + } + + [Fact] + public void CreatingARemoteAddsADefaultFetchRefSpec() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var remote = repo.Network.Remotes.Add("one", "http://github.com/up/stream"); + Assert.Equal("+refs/heads/*:refs/remotes/one/*", remote.RefSpecs.Single().Specification); + } + } + + [Fact] + public void CanCreateARemoteWithASpecifiedFetchRefSpec() + { + 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/*"); + Assert.Equal("+refs/heads/*:refs/remotes/grmpf/*", remote.RefSpecs.Single().Specification); + } + } + + [Fact] + public void CanDeleteExistingRemote() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/origin/*")); + + repo.Network.Remotes.Remove("origin"); + Assert.Null(repo.Network.Remotes["origin"]); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/origin/*")); + } + } + + [Fact] + public void CanDeleteNonExistingRemote() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Network.Remotes["i_dont_exist"]); + repo.Network.Remotes.Remove("i_dont_exist"); + } + } + + [Fact] + public void CanRenameExistingRemote() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + Assert.Null(repo.Network.Remotes["renamed"]); + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/origin/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + + repo.Network.Remotes.Rename("origin", "renamed", problem => Assert.True(false)); + Assert.Null(repo.Network.Remotes["origin"]); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/origin/*")); + + Assert.NotNull(repo.Network.Remotes["renamed"]); + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + } + } + + [Fact] + public void RenamingNonExistingRemoteThrows() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) { - Assert.Equal(expectedResult, repo.Network.Remotes.IsValidName(refname)); + Assert.Throws(() => + { + repo.Network.Remotes.Rename("i_dont_exist", "i_dont_either"); + }); } } + + [Fact] + public void ReportsRemotesWithNonDefaultRefSpecs() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(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)); + + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/nondefault/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/upstream/*")); + + Assert.Null(repo.Network.Remotes["origin"]); + Assert.NotNull(repo.Network.Remotes["nondefault"]); + } + } + + [Fact] + public void DoesNotReportRemotesWithAlreadyExistingRefSpec() + { + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + + repo.Refs.Add("refs/remotes/renamed/master", "32eab9cb1f450b5fe7ab663462b77d7f4b703344"); + + repo.Network.Remotes.Rename("origin", "renamed", problem => Assert.True(false)); + + Assert.NotEmpty(repo.Refs.FromGlob("refs/remotes/renamed/*")); + Assert.Empty(repo.Refs.FromGlob("refs/remotes/origin/*")); + + Assert.Null(repo.Network.Remotes["origin"]); + Assert.NotNull(repo.Network.Remotes["renamed"]); + } + } + + [Fact] + public void CanNotRenameWhenRemoteWithSameNameExists() + { + const string name = "upstream"; + const string url = "https://github.com/libgit2/libgit2sharp.git"; + + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Network.Remotes["origin"]); + repo.Network.Remotes.Add(name, url); + + Assert.Throws(() => repo.Network.Remotes.Rename("origin", "upstream")); + } + } + + [Theory] + [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) + { + 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 new file mode 100644 index 000000000..1b74995ca --- /dev/null +++ b/LibGit2Sharp.Tests/RemoveFixture.cs @@ -0,0 +1,190 @@ +using System; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class RemoveFixture : BaseFixture + { + [Theory] + /*** + * Test case: file exists in workdir and index, and has not been modified. + * '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.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.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.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.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.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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + + Assert.Equal(initialStatus, repo.RetrieveStatus(filename)); + Assert.Equal(existsBeforeRemove, File.Exists(fullpath)); + + if (throws) + { + Assert.Throws(() => Commands.Remove(repo, filename, removeFromWorkdir)); + Assert.Equal(count, repo.Index.Count); + } + else + { + Commands.Remove(repo, filename, removeFromWorkdir); + + Assert.Equal(count - 1, repo.Index.Count); + Assert.Equal(existsAfterRemove, File.Exists(fullpath)); + Assert.Equal(lastStatus, repo.RetrieveStatus(filename)); + } + } + } + + /*** + * Test case: modified file in wd, the modifications have already been promoted to the index, and + * new modifications have been made in the wd. + * 'git rm ' and 'git rm --cached ' both fail ("error: '' has staged content different from both the file and the HEAD") + */ + [Fact] + public void RemovingAModifiedFileWhoseChangesHaveBeenPromotedToTheIndexAndWithAdditionalModificationsMadeToItThrows() + { + const string filename = "modified_staged_file.txt"; + + var path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + + Assert.True(File.Exists(fullpath)); + + File.AppendAllText(fullpath, "additional content"); + Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(filename)); + + Assert.Throws(() => Commands.Remove(repo, filename)); + Assert.Throws(() => Commands.Remove(repo, filename, false)); + } + } + + [Fact] + public void CanRemoveAFolderThroughUsageOfPathspecsForNewlyAddedFiles() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + 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"))); + Commands.Remove(repo, "2", false); + + Assert.Equal(count - 5, repo.Index.Count); + } + } + + [Fact] + public void CanRemoveAFolderThroughUsageOfPathspecsForFilesAlreadyInTheIndexAndInTheHEAD() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + Assert.True(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); + Commands.Remove(repo, "1"); + + Assert.False(Directory.Exists(Path.Combine(repo.Info.WorkingDirectory, "1"))); + Assert.Equal(count - 1, repo.Index.Count); + } + } + + [Theory] + [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++) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Index[relativePath]); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + + 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.DeletedFromIndex)] + [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] + public void RemovingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileStatus status) + { + for (int i = 0; i < 2; i++) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Index[relativePath]); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + + Assert.Throws( + () => Commands.Remove(repo, relativePath, i % 2 == 0, new ExplicitPathsOptions())); + } + } + } + + [Fact] + public void RemovingFileWithBadParamsThrows() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + 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 3da774217..ef3e72f07 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -14,8 +14,9 @@ public class RepositoryFixture : BaseFixture [Fact] public void CanCreateBareRepo() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath, true)) + string repoPath = InitNewRepository(true); + + using (var repo = new Repository(repoPath)) { string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); @@ -23,41 +24,134 @@ public void CanCreateBareRepo() CheckGitConfigFile(dir); Assert.Null(repo.Info.WorkingDirectory); - Assert.Equal(scd.RootedDirectoryPath + Path.DirectorySeparatorChar, repo.Info.Path); + Assert.Equal(Path.GetFullPath(repoPath), repo.Info.Path); Assert.True(repo.Info.IsBare); + Assert.Throws(() => { var idx = repo.Index; }); + + AssertInitializedRepository(repo, "refs/heads/master"); - AssertInitializedRepository(repo); + repo.Refs.Add("HEAD", "refs/heads/orphan", true); + AssertInitializedRepository(repo, "refs/heads/orphan"); } } [Fact] public void AccessingTheIndexInABareRepoThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Assert.Throws(() => repo.Index); } } [Fact] - public void CanCreateStandardRepo() + public void CanCheckIfADirectoryLeadsToAValidRepository() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.DirectoryPath)) + Assert.False(Repository.IsValid(scd.DirectoryPath)); + + Directory.CreateDirectory(scd.DirectoryPath); + + 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() + { + string repoPath = InitNewRepository(); + + Assert.True(Repository.IsValid(repoPath)); + + using (var repo = new Repository(repoPath)) { + Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); + Assert.True(Repository.IsValid(repo.Info.Path)); + string dir = repo.Info.Path; Assert.True(Path.IsPathRooted(dir)); Assert.True(Directory.Exists(dir)); CheckGitConfigFile(dir); Assert.NotNull(repo.Info.WorkingDirectory); - Assert.Equal(Path.Combine(scd.RootedDirectoryPath, ".git" + Path.DirectorySeparatorChar), repo.Info.Path); + Assert.Equal(repoPath, repo.Info.Path); Assert.False(repo.Info.IsBare); AssertIsHidden(repo.Info.Path); - AssertInitializedRepository(repo); + AssertInitializedRepository(repo, "refs/heads/master"); + + repo.Refs.Add("HEAD", "refs/heads/orphan", true); + AssertInitializedRepository(repo, "refs/heads/orphan"); + } + } + + [Fact] + public void CanCreateStandardRepoAndSpecifyAFolderWhichWillContainTheNewlyCreatedGitDirectory() + { + var scd1 = BuildSelfCleaningDirectory(); + var scd2 = BuildSelfCleaningDirectory(); + + string repoPath = Repository.Init(scd1.DirectoryPath, scd2.DirectoryPath); + + Assert.True(Repository.IsValid(repoPath)); + + using (var repo = new Repository(repoPath)) + { + Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); + Assert.True(Repository.IsValid(repo.Info.Path)); + + Assert.False(repo.Info.IsBare); + + char sep = Path.DirectorySeparatorChar; + Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); + Assert.Equal(scd2.RootedDirectoryPath + sep + ".git" + sep, repo.Info.Path); + } + } + + [Fact] + public void CanCreateStandardRepoAndDirectlySpecifyAGitDirectory() + { + var scd1 = BuildSelfCleaningDirectory(); + var scd2 = BuildSelfCleaningDirectory(); + + var gitDir = Path.Combine(scd2.DirectoryPath, ".git/"); + + string repoPath = Repository.Init(scd1.DirectoryPath, gitDir); + + Assert.True(Repository.IsValid(repoPath)); + + using (var repo = new Repository(repoPath)) + { + Assert.True(Repository.IsValid(repo.Info.WorkingDirectory)); + Assert.True(Repository.IsValid(repo.Info.Path)); + + Assert.False(repo.Info.IsBare); + + char sep = Path.DirectorySeparatorChar; + Assert.Equal(scd1.RootedDirectoryPath + sep, repo.Info.WorkingDirectory); + Assert.Equal(Path.GetFullPath(gitDir), repo.Info.Path); } } @@ -72,6 +166,10 @@ 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)); @@ -83,10 +181,11 @@ public void CanFetchFromRemoteByName() string remoteName = "testRemote"; string url = "http://github.com/libgit2/TestGitRepository"; - var scd = BuildSelfCleaningDirectory(); - using (var repo = Repository.Init(scd.RootedDirectoryPath)) + string repoPath = InitNewRepository(); + + 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 @@ -113,13 +212,13 @@ public void CanFetchFromRemoteByName() } // Perform the actual fetch - repo.Fetch(remote.Name, 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, 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"]; @@ -131,12 +230,16 @@ public void CanFetchFromRemoteByName() [Fact] public void CanReinitARepository() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - using (Repository repository = Repository.Init(scd.DirectoryPath)) - using (Repository repository2 = Repository.Init(scd.DirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repository = new Repository(repoPath)) { - Assert.Equal(repository2.Info.Path, repository.Info.Path); + string repoPath2 = Repository.Init(repoPath, false); + + using (var repository2 = new Repository(repoPath2)) + { + Assert.Equal(repository2.Info.Path, repository.Info.Path); + } } } @@ -147,15 +250,15 @@ public void CreatingRepoWithBadParamsThrows() Assert.Throws(() => Repository.Init(null)); } - private static void AssertInitializedRepository(Repository repo) + private static void AssertInitializedRepository(IRepository repo, string expectedHeadTargetIdentifier) { Assert.NotNull(repo.Info.Path); Assert.False(repo.Info.IsHeadDetached); - Assert.True(repo.Info.IsHeadOrphaned); + Assert.True(repo.Info.IsHeadUnborn); Reference headRef = repo.Refs.Head; Assert.NotNull(headRef); - Assert.Equal("refs/heads/master", headRef.TargetIdentifier); + Assert.Equal(expectedHeadTargetIdentifier, headRef.TargetIdentifier); Assert.Null(headRef.ResolveToDirectReference()); Assert.NotNull(repo.Head); @@ -163,22 +266,25 @@ private static void AssertInitializedRepository(Repository repo) 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 Filter { Since = repo.Head }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new Filter { Since = "HEAD" }).Count()); - Assert.Equal(0, repo.Commits.QueryBy(new Filter { Since = "refs/heads/master" }).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); @@ -189,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); @@ -199,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); @@ -209,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); @@ -234,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); @@ -245,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); @@ -256,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); @@ -267,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)); @@ -278,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); @@ -290,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)); @@ -300,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)); @@ -311,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)); @@ -324,14 +441,13 @@ public void CanLookupWhithShortIdentifers() const string expectedAbbrevSha = "fe8410b"; const string expectedSha = expectedAbbrevSha + "6bfdf69ccfd4f397110d61f8070e46e40"; - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - string filePath = Path.Combine(repo.Info.WorkingDirectory, "new.txt"); - - File.WriteAllText(filePath, "one "); - repo.Index.Stage(filePath); + const string filename = "new.txt"; + Touch(repo.Info.WorkingDirectory, filename, "one "); + Commands.Stage(repo, filename); Signature author = Constants.Signature; Commit commit = repo.Commit("Initial commit", author, author); @@ -349,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^")); @@ -366,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); @@ -378,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)); @@ -392,12 +511,38 @@ 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() + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Lookup("e90810b")); + } + } + [Fact] public void CanDiscoverABareRepoGivenTheRepoPath() { @@ -429,59 +574,58 @@ 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { string branchName = repo.Head.CanonicalName; - Assert.False(repo.Info.IsHeadOrphaned); + Assert.False(repo.Info.IsHeadUnborn); repo.Refs.Add("HEAD", "refs/heads/orphan", true); - Assert.True(repo.Info.IsHeadOrphaned); + Assert.True(repo.Info.IsHeadUnborn); repo.Refs.Add("HEAD", branchName, true); - Assert.False(repo.Info.IsHeadOrphaned); + Assert.False(repo.Info.IsHeadUnborn); } } [Fact] public void QueryingTheRemoteForADetachedHeadBranchReturnsNull() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Checkout(repo.Head.Tip.Sha, CheckoutOptions.Force, null); + Commands.Checkout(repo, repo.Head.Tip.Sha, new CheckoutOptions() { CheckoutModifiers = CheckoutModifiers.Force }); Branch trackLocal = repo.Head; - Assert.Null(trackLocal.Remote); + Assert.Null(trackLocal.RemoteName); } } [Fact] public void ReadingEmptyRepositoryMessageReturnsNull() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { Assert.Null(repo.Info.Message); } @@ -490,12 +634,13 @@ public void ReadingEmptyRepositoryMessageReturnsNull() [Fact] public void CanReadRepositoryMessage() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); string testMessage = "This is a test message!"; - using (var repo = Repository.Init(scd.DirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { - File.WriteAllText(Path.Combine(repo.Info.Path, "MERGE_MSG"), testMessage); + Touch(repo.Info.Path, "MERGE_MSG", testMessage); Assert.Equal(testMessage, repo.Info.Message); } @@ -504,9 +649,9 @@ public void CanReadRepositoryMessage() [Fact] public void AccessingADeletedHeadThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { Assert.NotNull(repo.Head); @@ -515,5 +660,120 @@ public void AccessingADeletedHeadThrows() Assert.Throws(() => repo.Head); } } + + [Fact] + public void CanDetectShallowness() + { + var path = Sandbox(ShallowTestRepoPath); + using (var repo = new Repository(path)) + { + Assert.True(repo.Info.IsShallow); + } + + 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 2cd3153a3..46863f44d 100644 --- a/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryOptionsFixture.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -30,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); } } @@ -42,83 +44,94 @@ 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 CanProvideADifferentWorkDirToAStandardRepo() + public void CanOpenABareRepoWithOptions() { - var scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + var options = new RepositoryOptions { }; + + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path, options)) + { + Assert.True(repo.Info.IsBare); + } + } - using (var repo = new Repository(scd.DirectoryPath)) + [Fact] + public void CanProvideADifferentWorkDirToAStandardRepo() + { + 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 }; - scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(scd.DirectoryPath, options)) + 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 scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(scd.DirectoryPath)) + 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 }; - scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(scd.DirectoryPath, options)) + 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() { - var scd = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(scd.DirectoryPath)) + 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(scd.DirectoryPath); + string commitSha = MeanwhileInAnotherDimensionAnEvilMastermindIsAtWork(path); Branch newHead = repo.Head; 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")); } } @@ -130,118 +143,39 @@ private string MeanwhileInAnotherDimensionAnEvilMastermindIsAtWork(string workin { Assert.Equal(Path.GetFullPath(newWorkdir) + Path.DirectorySeparatorChar, Path.GetFullPath(sneakyRepo.Info.WorkingDirectory)); - sneakyRepo.Reset(ResetOptions.Mixed, sneakyRepo.Head.Tip.Sha); + sneakyRepo.Reset(ResetMode.Mixed, sneakyRepo.Head.Tip.Sha); - var filepath = Path.Combine(sneakyRepo.Info.WorkingDirectory, "zomg.txt"); - File.WriteAllText(filepath, "I'm being sneaked in!\n"); + const string filename = "zomg.txt"; + Touch(sneakyRepo.Info.WorkingDirectory, filename, "I'm being sneaked in!\n"); - sneakyRepo.Index.Stage(filepath); - return sneakyRepo.Commit("Tadaaaa!", DummySignature, DummySignature).Sha; + 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 CanProvideDifferentWorkingDirOnInit() + public void CanCommitOnBareRepository() { + string repoPath = InitNewRepository(true); SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - var options = new RepositoryOptions {WorkingDirectoryPath = newWorkdir}; + string workPath = Path.Combine(scd.RootedDirectoryPath, "work"); + Directory.CreateDirectory(workPath); - using (var repo = Repository.Init(scd.DirectoryPath, false, options)) + var repositoryOptions = new RepositoryOptions { - Assert.Equal(Path.GetFullPath(newWorkdir) + Path.DirectorySeparatorChar, repo.Info.WorkingDirectory); - } - } - - [Fact] - public void CanProvideDifferentConfigurationFilesOnInit() - { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - var options = BuildFakeConfigs(scd); - - using (var repo = Repository.Init(scd.DirectoryPath, false, options)) - { - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); - Assert.Equal("global", repo.Config.Get("woot.this-rocks").Value); - Assert.Equal(42, repo.Config.Get("wow.man-I-am-totally-global").Value); - - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Xdg)); - Assert.Equal("xdg", repo.Config.Get("woot.this-rocks", ConfigurationLevel.Xdg).Value); - - Assert.True(repo.Config.HasConfig(ConfigurationLevel.System)); - Assert.Equal("system", repo.Config.Get("woot.this-rocks", ConfigurationLevel.System).Value); - } - } - - [Fact] - public void CanProvideDifferentWorkingDirOnClone() - { - string url = "https://github.com/libgit2/TestGitRepository"; - var scd = BuildSelfCleaningDirectory(); - var options = new RepositoryOptions { WorkingDirectoryPath = newWorkdir }; - - using (var repo = Repository.Clone(url, scd.DirectoryPath, false, true, null, null, options)) - { - Assert.Equal(Path.GetFullPath(newWorkdir) + Path.DirectorySeparatorChar, repo.Info.WorkingDirectory); - } - } - - [Fact] - public void CanProvideDifferentConfigurationFilesOnClone() - { - string url = "https://github.com/libgit2/TestGitRepository"; - var scd = BuildSelfCleaningDirectory(); - var configScd = BuildSelfCleaningDirectory(); - var options = BuildFakeConfigs(configScd); + WorkingDirectoryPath = workPath, + IndexPath = Path.Combine(scd.RootedDirectoryPath, "index") + }; - using (var repo = Repository.Clone(url, scd.DirectoryPath, false, true, null, null, options)) + using (var repo = new Repository(repoPath, repositoryOptions)) { - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Global)); - Assert.Equal("global", repo.Config.Get("woot.this-rocks").Value); - Assert.Equal(42, repo.Config.Get("wow.man-I-am-totally-global").Value); - - Assert.True(repo.Config.HasConfig(ConfigurationLevel.Xdg)); - Assert.Equal("xdg", repo.Config.Get("woot.this-rocks", ConfigurationLevel.Xdg).Value); + const string relativeFilepath = "test.txt"; + Touch(repo.Info.WorkingDirectory, relativeFilepath, "test\n"); + Commands.Stage(repo, relativeFilepath); - Assert.True(repo.Config.HasConfig(ConfigurationLevel.System)); - Assert.Equal("system", repo.Config.Get("woot.this-rocks", ConfigurationLevel.System).Value); + Assert.NotNull(repo.Commit("Initial commit", Constants.Signature, Constants.Signature)); + Assert.Single(repo.Head.Commits); + Assert.Single(repo.Commits); } } } diff --git a/LibGit2Sharp.Tests/ResetHeadFixture.cs b/LibGit2Sharp.Tests/ResetHeadFixture.cs index f9b46c6c0..5fb841ae0 100644 --- a/LibGit2Sharp.Tests/ResetHeadFixture.cs +++ b/LibGit2Sharp.Tests/ResetHeadFixture.cs @@ -14,22 +14,23 @@ public class ResetHeadFixture : BaseFixture [InlineData(false)] public void ResetANewlyInitializedRepositoryThrows(bool isBare) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(isBare); - using (var repo = Repository.Init(scd.DirectoryPath, isBare)) + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.Reset(ResetOptions.Soft)); + Assert.Throws(() => repo.Reset(ResetMode.Soft)); } } [Fact] public void SoftResetToTheHeadOfARepositoryDoesNotChangeTheTargetOfTheHead() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Branch oldHead = repo.Head; - repo.Reset(ResetOptions.Soft); + repo.Reset(ResetMode.Soft); Assert.Equal(oldHead, repo.Head); } @@ -38,13 +39,12 @@ public void SoftResetToTheHeadOfARepositoryDoesNotChangeTheTargetOfTheHead() [Fact] public void SoftResetToAParentCommitChangesTheTargetOfTheHead() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { var headCommit = repo.Head.Tip; var firstCommitParent = headCommit.Parents.First(); - repo.Reset(ResetOptions.Soft, firstCommitParent); + repo.Reset(ResetMode.Soft, firstCommitParent); Assert.Equal(firstCommitParent, repo.Head.Tip); } @@ -53,12 +53,11 @@ public void SoftResetToAParentCommitChangesTheTargetOfTheHead() [Fact] public void SoftResetSetsTheHeadToTheDereferencedCommitOfAChainedTag() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["test"]; - repo.Reset(ResetOptions.Soft, tag.CanonicalName); + repo.Reset(ResetMode.Soft, tag.CanonicalName); Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", repo.Head.Tip.Sha); } } @@ -66,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(ResetOptions.Soft, (string)null)); - Assert.Throws(() => repo.Reset(ResetOptions.Soft, (Commit)null)); - Assert.Throws(() => repo.Reset(ResetOptions.Soft, "")); - Assert.Throws(() => repo.Reset(ResetOptions.Soft, Constants.UnknownSha)); - Assert.Throws(() => repo.Reset(ResetOptions.Soft, repo.Head.Tip.Tree.Sha)); + 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)); } } @@ -80,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] @@ -92,9 +92,9 @@ public void SoftResetSetsTheDetachedHeadToTheSpecifiedCommit() private void AssertSoftReset(Func branchIdentifierRetriever, bool shouldHeadBeDetached, Func expectedHeadNameRetriever) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) { FeedTheRepository(repo); @@ -102,104 +102,161 @@ 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(ResetOptions.Soft, tag.CanonicalName); - Assert.Equal(expectedHeadName, repo.Head.Name); + repo.Reset(ResetMode.Soft, tag.CanonicalName); + 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", + string.Format("reset: moving to {0}", tag.Target.Sha), + oldHeadId, + tag.Target.Id, + Constants.Identity, before); + + if (!shouldHeadBeDetached) + { + AssertRefLogEntry(repo, branch.CanonicalName, + string.Format("reset: moving to {0}", tag.Target.Sha), + oldHeadId, + tag.Target.Id, + Constants.Identity, before); + } + + before = DateTimeOffset.Now.TruncateMilliseconds(); /* Reset --soft the Head to a commit through its sha */ - repo.Reset(ResetOptions.Soft, branch.Tip.Sha); - 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", + string.Format("reset: moving to {0}", branch.Tip.Sha), + tag.Target.Id, + branch.Tip.Id, + Constants.Identity, before); + + if (!shouldHeadBeDetached) + { + AssertRefLogEntry(repo, branch.CanonicalName, + string.Format("reset: moving to {0}", branch.Tip.Sha), + tag.Target.Id, + branch.Tip.Id, + Constants.Identity, before); + } } } - private static void FeedTheRepository(Repository repo) + private static void FeedTheRepository(IRepository repo) { - string fullPath = Path.Combine(repo.Info.WorkingDirectory, "a.txt"); - File.WriteAllText(fullPath, "Hello\n"); - repo.Index.Stage(fullPath); + string fullPath = Touch(repo.Info.WorkingDirectory, "a.txt", "Hello\n"); + 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] public void MixedResetRefreshesTheIndex() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath, new RepositoryOptions { Identity = Constants.Identity })) { FeedTheRepository(repo); + var oldHeadId = repo.Head.Tip.Id; + Tag tag = repo.Tags["mytag"]; - repo.Reset(ResetOptions.Mixed, tag.CanonicalName); + var before = DateTimeOffset.Now.TruncateMilliseconds(); + + repo.Reset(ResetMode.Mixed, tag.CanonicalName); + + Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus("a.txt")); - Assert.Equal(FileStatus.Modified, repo.Index.RetrieveStatus("a.txt")); + AssertRefLogEntry(repo, "HEAD", + string.Format("reset: moving to {0}", tag.Target.Sha), + oldHeadId, + tag.Target.Id, + Constants.Identity, before); + + AssertRefLogEntry(repo, "refs/heads/mybranch", + string.Format("reset: moving to {0}", tag.Target.Sha), + 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(ResetOptions.Mixed)); + Assert.Throws(() => repo.Reset(ResetMode.Mixed)); } } [Fact] public void HardResetInABareRepositoryThrows() { - using (var repo = new Repository(BareTestRepoPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Reset(ResetOptions.Hard)); + Assert.Throws(() => repo.Reset(ResetMode.Hard)); } } [Fact] public void HardResetUpdatesTheContentOfTheWorkingDirectory() { - var clone = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + bool progressCalled = false; - using (var repo = new Repository(clone.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { var names = new DirectoryInfo(repo.Info.WorkingDirectory).GetFileSystemInfos().Select(fsi => fsi.Name).ToList(); - names.Sort(StringComparer.Ordinal); File.Delete(Path.Combine(repo.Info.WorkingDirectory, "README")); - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, "WillNotBeRemoved.txt"), "content\n"); + Touch(repo.Info.WorkingDirectory, "WillNotBeRemoved.txt", "content\n"); Assert.True(names.Count > 4); - repo.Reset(ResetOptions.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 9f69dae3b..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; @@ -10,47 +9,37 @@ public class ResetIndexFixture : BaseFixture [Fact] public void ResetANewlyInitializedBareRepositoryThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(true); - using (Repository repo = Repository.Init(scd.DirectoryPath, true)) + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.Reset()); - } - } - - [Fact] - public void ResetANewlyInitializedNonBareRepositoryThrows() - { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - - using (Repository repo = Repository.Init(scd.DirectoryPath, false)) - { - 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,83 +50,117 @@ private static bool IsStaged(StatusEntry entry) [Fact] public void ResetTheIndexWithTheHeadUnstagesEverything() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.DirectoryPath)) + 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()); - repo.Reset(); + var reflogEntriesCount = repo.Refs.Log(repo.Refs.Head).Count(); + + repo.Index.Replace(repo.Head.Tip); + + RepositoryStatus newStatus = repo.RetrieveStatus(); + Assert.DoesNotContain(newStatus, IsStaged); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(0, newStatus.Where(IsStaged).Count()); + // Assert that no reflog entry is created + Assert.Equal(reflogEntriesCount, repo.Refs.Log(repo.Refs.Head).Count()); } } [Fact] - public void CanResetTheIndexToTheContentOfACommitWithCommitishAsArgument() + public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Reset("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()); - Assert.Equal(expected, newStatus.Removed); + Assert.Equal(expected, newStatus.Removed.Select(s => s.FilePath)); } } [Fact] - public void CanResetTheIndexToTheContentOfACommitWithCommitAsArgument() + public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgumentAndLaxUnmatchedExplicitPathsValidation() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.DirectoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Reset(repo.Lookup("be3563a")); - - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); + repo.Index.Replace(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, + new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); - 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); + Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", repo.Index["README"].Id.Sha); + Assert.Equal("fa49b077972391ad58037050f2a75f74e3671e92", repo.Index["new.txt"].Id.Sha); } } [Fact] - public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommitishAsArgument() + public void ResettingTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgumentAndStrictUnmatchedPathspecsValidationThrows() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + using (var repo = new Repository(SandboxStandardTestRepo())) + { + Assert.Throws(() => + repo.Index.Replace(repo.Lookup("5b5b025"), new[] { "new.txt", "non-existent-path-28.txt" }, new ExplicitPathsOptions())); + } + } - using (var repo = new Repository(path.DirectoryPath)) + [Fact] + public void CanResetTheIndexWhenARenameExists() + { + using (var repo = new Repository(SandboxStandardTestRepo())) { - repo.Reset("5b5b025", new[]{ "new.txt" }); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + repo.Index.Replace(repo.Lookup("32eab9c")); - Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", repo.Index["README"].Id.Sha); - Assert.Equal("fa49b077972391ad58037050f2a75f74e3671e92", repo.Index["new.txt"].Id.Sha); + RepositoryStatus status = repo.RetrieveStatus(); + Assert.DoesNotContain(status, IsStaged); } } [Fact] - public void CanResetTheIndexToASubsetOfTheContentOfACommitWithCommitAsArgument() + public void CanResetSourceOfARenameInIndex() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + using (var repo = new Repository(SandboxStandardTestRepo())) + { + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + + 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.Replace(repo.Lookup("32eab9c"), new string[] { "branch_file.txt" }); - using (var repo = new Repository(path.DirectoryPath)) + 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(SandboxStandardTestRepo())) { - repo.Reset(repo.Lookup("5b5b025"), new[] { "new.txt" }); + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); - Assert.Equal("a8233120f6ad708f843d861ce2b7228ec4e3dec6", repo.Index["README"].Id.Sha); - Assert.Equal("fa49b077972391ad58037050f2a75f74e3671e92", repo.Index["new.txt"].Id.Sha); + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); + Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); + + repo.Index.Replace(repo.Lookup("32eab9c"), new string[] { "renamed_branch_file.txt" }); + + 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 new file mode 100644 index 000000000..6b70b2273 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/.gitignore @@ -0,0 +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/assume_unchanged_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/assume_unchanged_wd/dot_git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/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/expected/f8d44d7...7252fe2/full-0-3.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-0-3.diff new file mode 100644 index 000000000..8f1024340 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-0-3.diff @@ -0,0 +1,32 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,0 +2 @@ ++2 +@@ -11 +12 @@ +-12 ++11 +@@ -15,0 +17 @@ ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-0-4.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-0-4.diff new file mode 100644 index 000000000..ac0db32e5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-0-4.diff @@ -0,0 +1,35 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,0 +2 @@ ++2 +@@ -11,5 +12,6 @@ +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-1-1.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-1-1.diff new file mode 100644 index 000000000..0447955c4 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-1-1.diff @@ -0,0 +1,37 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,2 +1,3 @@ + 1 ++2 + 3 +@@ -10,3 +11,3 @@ + 10 +-12 ++11 + 12 +@@ -15 +16,2 @@ + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-1-2.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-1-2.diff new file mode 100644 index 000000000..6f1be86c9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-1-2.diff @@ -0,0 +1,38 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,2 +1,3 @@ + 1 ++2 + 3 +@@ -10,6 +11,7 @@ + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-2-4.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-2-4.diff new file mode 100644 index 000000000..a470e39c0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-2-4.diff @@ -0,0 +1,40 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,3 +1,4 @@ + 1 ++2 + 3 + 4 +@@ -9,7 +10,8 @@ + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-2-5.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-2-5.diff new file mode 100644 index 000000000..ae602bcc5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-2-5.diff @@ -0,0 +1,44 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-3-2.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-3-2.diff new file mode 100644 index 000000000..2037795d1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-3-2.diff @@ -0,0 +1,42 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,4 +1,5 @@ + 1 ++2 + 3 + 4 + 5 +@@ -8,8 +9,9 @@ + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-3-3.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-3-3.diff new file mode 100644 index 000000000..ae602bcc5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-3-3.diff @@ -0,0 +1,44 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-4-0.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-4-0.diff new file mode 100644 index 000000000..d624e0d73 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-4-0.diff @@ -0,0 +1,44 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,5 +1,6 @@ + 1 ++2 + 3 + 4 + 5 + 6 +@@ -7,9 +8,10 @@ + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/full-4-1.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-4-1.diff new file mode 100644 index 000000000..ae602bcc5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/full-4-1.diff @@ -0,0 +1,44 @@ +diff --git a/my-name-does-not-feel-right.txt b/my-name-does-not-feel-right.txt +deleted file mode 100644 +index e8953ab..0000000 +--- a/my-name-does-not-feel-right.txt ++++ /dev/null +@@ -1,4 +0,0 @@ +-That's a terrible name! +-I don't like it. +-People look down at me and laugh. :-( +-Really!!!! +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 +diff --git a/super-file.txt b/super-file.txt +new file mode 100644 +index 0000000..16bdf1d +--- /dev/null ++++ b/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/expected/f8d44d7...7252fe2/numbers.txt-0-3.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-0-3.diff new file mode 100644 index 000000000..c14970b01 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-0-3.diff @@ -0,0 +1,11 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,0 +2 @@ ++2 +@@ -11 +12 @@ +-12 ++11 +@@ -15,0 +17 @@ ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-0-4.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-0-4.diff new file mode 100644 index 000000000..4c2236c7b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-0-4.diff @@ -0,0 +1,14 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,0 +2 @@ ++2 +@@ -11,5 +12,6 @@ +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-1.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-1.diff new file mode 100644 index 000000000..5ad039099 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-1.diff @@ -0,0 +1,16 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,2 +1,3 @@ + 1 ++2 + 3 +@@ -10,3 +11,3 @@ + 10 +-12 ++11 + 12 +@@ -15 +16,2 @@ + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-2.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-2.diff new file mode 100644 index 000000000..7a5fda643 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-1-2.diff @@ -0,0 +1,17 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,2 +1,3 @@ + 1 ++2 + 3 +@@ -10,6 +11,7 @@ + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-4.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-4.diff new file mode 100644 index 000000000..9311b08fc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-4.diff @@ -0,0 +1,19 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,3 +1,4 @@ + 1 ++2 + 3 + 4 +@@ -9,7 +10,8 @@ + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-5.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-5.diff new file mode 100644 index 000000000..26e01b51f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-2-5.diff @@ -0,0 +1,23 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-2.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-2.diff new file mode 100644 index 000000000..9f55b2e05 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-2.diff @@ -0,0 +1,21 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,4 +1,5 @@ + 1 ++2 + 3 + 4 + 5 +@@ -8,8 +9,9 @@ + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-3.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-3.diff new file mode 100644 index 000000000..26e01b51f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-3-3.diff @@ -0,0 +1,23 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-0.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-0.diff new file mode 100644 index 000000000..9ec9021ef --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-0.diff @@ -0,0 +1,23 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,5 +1,6 @@ + 1 ++2 + 3 + 4 + 5 + 6 +@@ -7,9 +8,10 @@ + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-1.diff b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-1.diff new file mode 100644 index 000000000..26e01b51f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/expected/f8d44d7...7252fe2/numbers.txt-4-1.diff @@ -0,0 +1,23 @@ +diff --git a/numbers.txt b/numbers.txt +index 7909961..4e935b7 100644 +--- a/numbers.txt ++++ b/numbers.txt +@@ -1,15 +1,17 @@ + 1 ++2 + 3 + 4 + 5 + 6 + 7 + 7.2 + 8 + 9 + 10 +-12 ++11 + 12 + 13 + 14 + 15 ++16 diff --git a/LibGit2Sharp.Tests/Resources/expected_archives/commit_with_directory.tar b/LibGit2Sharp.Tests/Resources/expected_archives/commit_with_directory.tar new file mode 100644 index 000000000..0933834c5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/expected_archives/commit_with_directory.tar differ diff --git a/LibGit2Sharp.Tests/Resources/expected_archives/tree_with_directory.tar b/LibGit2Sharp.Tests/Resources/expected_archives/tree_with_directory.tar new file mode 100644 index 000000000..f25d0d973 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/expected_archives/tree_with_directory.tar differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/a.txt b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/a.txt new file mode 100644 index 000000000..610b16886 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/a.txt @@ -0,0 +1,2 @@ +this is file a.txt. +It has 2 lines and this is the last line. diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/b.txt b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/b.txt new file mode 100644 index 000000000..ec4a0198e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/b.txt @@ -0,0 +1,3 @@ +this is file b.txt. +it has 3 lines. +this is the last line. \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_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/merge_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index new file mode 100644 index 000000000..1d91f96f1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs new file mode 100644 index 000000000..d3a074d46 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/info/refs @@ -0,0 +1,6 @@ +0771966a0d112d3787006a6fda5b6226a770e15a refs/heads/conflicts +4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 refs/heads/fast_forward +83cebf5389a4adbcb80bda6b68513caee4559802 refs/heads/master +625186280ed2a6ec9b65d250ed90cf2e4acef957 refs/heads/normal_merge +24434077dec097c1203ef9e1345c0545c190936a refs/heads/rename +2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 refs/heads/rename_source 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/objects/info/packs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs new file mode 100644 index 000000000..2dd2e88a9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack + diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx new file mode 100644 index 000000000..72d56baa5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.idx differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack new file mode 100644 index 000000000..037aa5388 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/objects/pack/pack-f9b2f231d5e59d4a265578d02283f848a5dc4694.pack differ diff --git a/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs new file mode 100644 index 000000000..7930b20e4 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/packed-refs @@ -0,0 +1,7 @@ +# pack-refs with: peeled fully-peeled +0771966a0d112d3787006a6fda5b6226a770e15a refs/heads/conflicts +4dfaa1500526214ae7b33f9b2c1144ca8b6b1f53 refs/heads/fast_forward +83cebf5389a4adbcb80bda6b68513caee4559802 refs/heads/master +625186280ed2a6ec9b65d250ed90cf2e4acef957 refs/heads/normal_merge +24434077dec097c1203ef9e1345c0545c190936a refs/heads/rename +2bc71d0e8acfbb9fd1cc2d9d48c23dbf8aea52c9 refs/heads/rename_source 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/merge_testrepo_wd/dot_git/refs/heads/normal_merge2 b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/normal_merge2 new file mode 100644 index 000000000..ecc94f034 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/merge_testrepo_wd/dot_git/refs/heads/normal_merge2 @@ -0,0 +1 @@ +625186280ed2a6ec9b65d250ed90cf2e4acef957 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/0a-no-change.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0a-no-change.txt new file mode 100644 index 000000000..68c6c84b0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0a-no-change.txt @@ -0,0 +1,21 @@ +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. +there is no change in this file. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-duplicated-in-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-duplicated-in-ours.txt new file mode 100644 index 000000000..f0ce2b8e4 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-duplicated-in-ours.txt @@ -0,0 +1,23 @@ +this file will be rewritten in +ours, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +ours, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +ours, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +ours, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-rewritten-in-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-rewritten-in-ours.txt new file mode 100644 index 000000000..8aac75de2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0b-rewritten-in-ours.txt @@ -0,0 +1,31 @@ +this file was rewritten in the +ours branch, and the original +file was copied to the file +0b-duplicated-in-ours.txt. that +duplicated file will not be +eligible for rename detection. + +<<<<<<< HEAD +this file was rewritten in the +ours branch, and the original +file was copied to the file +0b-duplicated-in-ours.txt. that +duplicated file will not be +eligible for rename detection. +======= +note that it has also been edited +in the theirs branch + +this file will be rewritten in +ours, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. +>>>>>>> rename_conflict_theirs + +this file was rewritten in the +ours branch, and the original +file was copied to the file +0b-duplicated-in-ours.txt. that +duplicated file will not be +eligible for rename detection. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-duplicated-in-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-duplicated-in-theirs.txt new file mode 100644 index 000000000..2f5612010 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-duplicated-in-theirs.txt @@ -0,0 +1,23 @@ +this file will be rewritten in +theirs, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +theirs, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +theirs, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. + +this file will be rewritten in +theirs, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-rewritten-in-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-rewritten-in-theirs.txt new file mode 100644 index 000000000..7edc72632 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/0c-rewritten-in-theirs.txt @@ -0,0 +1,39 @@ +this file was rewritten in the +theirs branch, and the original +file was copied to the file +0c-duplicated-in-theirs.txt. that +duplicated file will not be +eligible for rename detection. + +<<<<<<< HEAD +note that is has also been edited +in the ours branch + +this file will be rewritten in +theirs, and its contents will be +duplicated in another file. +but it will not be eligible for +rename detection. +======= +this file was rewritten in the +theirs branch, and the original +file was copied to the file +0c-duplicated-in-theirs.txt. that +duplicated file will not be +eligible for rename detection. +>>>>>>> rename_conflict_theirs + +this file was rewritten in the +theirs branch, and the original +file was copied to the file +0c-duplicated-in-theirs.txt. that +duplicated file will not be +eligible for rename detection. + +this file was rewritten in the +theirs branch, and the original +file was copied to the file +0c-duplicated-in-theirs.txt. that +duplicated file will not be +eligible for rename detection. + diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours-edited-in-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours-edited-in-theirs.txt new file mode 100644 index 000000000..0d872f8e8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours-edited-in-theirs.txt @@ -0,0 +1,25 @@ +this will be renamed in ours +and edited in theirs + +(indeed, it is edited in theirs) + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs + +this will be renamed in ours +and edited in theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours.txt new file mode 100644 index 000000000..d0d4594e1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1a-newname-in-ours.txt @@ -0,0 +1,16 @@ +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours +this will be renamed in ours diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs-edited-in-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs-edited-in-ours.txt new file mode 100644 index 000000000..ed9523e62 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs-edited-in-ours.txt @@ -0,0 +1,25 @@ +this will be renamed in theirs +and edited in ours + +(and lo, it is edited in ours) + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours + +this will be renamed in theirs +and edited in ours diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs.txt new file mode 100644 index 000000000..2b5f1f181 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/1b-newname-in-theirs.txt @@ -0,0 +1,14 @@ +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs +this will be renamed in theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/2-newname-in-both.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/2-newname-in-both.txt new file mode 100644 index 000000000..178940b45 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/2-newname-in-both.txt @@ -0,0 +1,20 @@ +this file will be renamed +in both branches + +this file will be renamed +in both branches + +this file will be renamed +in both branches + +this file will be renamed +in both branches + +this file will be renamed +in both branches + +this file will be renamed +in both branches + +this file will be renamed +in both branches diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/3a-newname-in-ours-deleted-in-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/3a-newname-in-ours-deleted-in-theirs.txt new file mode 100644 index 000000000..18cb316b1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/3a-newname-in-ours-deleted-in-theirs.txt @@ -0,0 +1,20 @@ +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/3b-newname-in-theirs-deleted-in-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/3b-newname-in-theirs-deleted-in-ours.txt new file mode 100644 index 000000000..36219b493 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/3b-newname-in-theirs-deleted-in-ours.txt @@ -0,0 +1,23 @@ +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs + +this will be renamed in ours +and deleted in theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~HEAD new file mode 100644 index 000000000..227792b52 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~HEAD @@ -0,0 +1,24 @@ +this will be renamed in ours +and a file with the same name +will be independently added +in theirs + +this will be renamed in ours +and a file with the same name +will be independently added +in theirs + +this will be renamed in ours +and a file with the same name +will be independently added +in theirs + +this will be renamed in ours +and a file with the same name +will be independently added +in theirs + +this will be renamed in ours +and a file with the same name +will be independently added +in theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs new file mode 100644 index 000000000..8b5b53cb2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs @@ -0,0 +1,23 @@ +this file was rewritten in +theirs and will appear as an +independent add of a file + +this file was rewritten in +theirs and will appear as an +independent add of a file + +this file was rewritten in +theirs and will appear as an +independent add of a file + +this file was rewritten in +theirs and will appear as an +independent add of a file + +this file was rewritten in +theirs and will appear as an +independent add of a file + +this file was rewritten in +theirs and will appear as an +independent add of a file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~HEAD new file mode 100644 index 000000000..de872ee36 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~HEAD @@ -0,0 +1,19 @@ +this file was rewritten in +ours and will appear as an +independent add of a file + +this file was rewritten in +ours and will appear as an +independent add of a file + +this file was rewritten in +ours and will appear as an +independent add of a file + +this file was rewritten in +ours and will appear as an +independent add of a file + +this file was rewritten in +ours and will appear as an +independent add of a file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs new file mode 100644 index 000000000..98d52d07c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/4b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs @@ -0,0 +1,24 @@ +this will be renamed in theirs +and a file with the same name +will be independently added +in ours + +this will be renamed in theirs +and a file with the same name +will be independently added +in ours + +this will be renamed in theirs +and a file with the same name +will be independently added +in ours + +this will be renamed in theirs +and a file with the same name +will be independently added +in ours + +this will be renamed in theirs +and a file with the same name +will be independently added +in ours diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~HEAD new file mode 100644 index 000000000..d3719a5ae --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~HEAD @@ -0,0 +1,18 @@ +This file will be +renamed in 'ours', +and a file of the +same name will be +added in 'theirs'. + +The original file +will exist in the +'theirs' side as +well. + +Expected index +entries are: + +1 A +3 A +2 B +3 B diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs new file mode 100644 index 000000000..98ba4205f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5a-newname-in-ours-added-in-theirs.txt~rename_conflict_theirs @@ -0,0 +1,9 @@ +This file has +the same name +as a file that +was renamed in +the other branch. + +This will, upon +merging, lead to +a rename conflict. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~HEAD new file mode 100644 index 000000000..385c8a0f2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~HEAD @@ -0,0 +1,10 @@ +This is the file +in 'theirs' that +was added with +the same name +that another file +was renamed to! + +This will cause +a rename/add +conflict! diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs new file mode 100644 index 000000000..632471253 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/5b-newname-in-theirs-added-in-ours.txt~rename_conflict_theirs @@ -0,0 +1,19 @@ +In the 'theirs' branch, +this file will be renamed +and in the 'ours' branch, +a file of the same name +will be added. This file +will continue to exist +in the 'ours' branch, +leading to a rename/add +conflict. + +In the 'theirs' branch, +this file will be renamed +and in the 'ours' branch, +a file of the same name +will be added. This file +will continue to exist +in the 'ours' branch, +leading to a rename/add +conflict. diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-ours.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-ours.txt new file mode 100644 index 000000000..d8fa77b68 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-ours.txt @@ -0,0 +1,23 @@ +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-theirs.txt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-theirs.txt new file mode 100644 index 000000000..d8fa77b68 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/6-both-renamed-1-to-2-theirs.txt @@ -0,0 +1,23 @@ +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches + +this file will be renamed +(differently) in both the +ours and the theirs branches diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~HEAD new file mode 100644 index 000000000..b42712cfe --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~HEAD @@ -0,0 +1,23 @@ +this is one file that will be renamed +to a common name in the ours and theirs +branches + +this is one file that will be renamed +to a common name in the ours and theirs +branches + +this is one file that will be renamed +to a common name in the ours and theirs +branches + +this is one file that will be renamed +to a common name in the ours and theirs +branches + +this is one file that will be renamed +to a common name in the ours and theirs +branches + +this is one file that will be renamed +to a common name in the ours and theirs +branches diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~rename_conflict_theirs new file mode 100644 index 000000000..b69fe837e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/7-both-renamed.txt~rename_conflict_theirs @@ -0,0 +1,23 @@ +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches + - +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches + - +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches + - +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches + - +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches + - +this is a different file that will +also be renamed to a common file name +in the ours and theirs branches diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/COMMIT_EDITMSG b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/COMMIT_EDITMSG new file mode 100644 index 000000000..245b18a2c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/COMMIT_EDITMSG @@ -0,0 +1 @@ +rename conflict theirs diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/HEAD new file mode 100644 index 000000000..b7f23f5ed --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/rename_conflict_ours diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_HEAD new file mode 100644 index 000000000..130989399 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_HEAD @@ -0,0 +1 @@ +a802e06f1782a9645b9851bc7202cee74a8a4972 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MODE b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MODE new file mode 100644 index 000000000..fe93f7198 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MODE @@ -0,0 +1 @@ +no-ff \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MSG b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MSG new file mode 100644 index 000000000..6121863d1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/MERGE_MSG @@ -0,0 +1,23 @@ +Merge branch 'rename_conflict_theirs' + +Conflicts: + 0b-rewritten-in-ours.txt + 0c-rewritten-in-theirs.txt + 3a-newname-in-ours-deleted-in-theirs.txt + 3a-renamed-in-ours-deleted-in-theirs.txt + 3b-newname-in-theirs-deleted-in-ours.txt + 3b-renamed-in-theirs-deleted-in-ours.txt + 4a-newname-in-ours-added-in-theirs.txt + 4a-renamed-in-ours-added-in-theirs.txt + 4b-newname-in-theirs-added-in-ours.txt + 4b-renamed-in-theirs-added-in-ours.txt + 5a-newname-in-ours-added-in-theirs.txt + 5a-renamed-in-ours-added-in-theirs.txt + 5b-newname-in-theirs-added-in-ours.txt + 5b-renamed-in-theirs-added-in-ours.txt + 6-both-renamed-1-to-2-ours.txt + 6-both-renamed-1-to-2-theirs.txt + 6-both-renamed-1-to-2.txt + 7-both-renamed-side-1.txt + 7-both-renamed-side-2.txt + 7-both-renamed.txt diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/ORIG_HEAD new file mode 100644 index 000000000..a1c50dce8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/ORIG_HEAD @@ -0,0 +1 @@ +34bfafff88eaf118402b44e6f3e2dbbf1a582b05 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/config new file mode 100644 index 000000000..623a0bf58 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/config @@ -0,0 +1,11 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + ignorecase = true + symlinks = false +[submodule "submodule"] + url = ../submodule +[merge] + conflictstyle = merge diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/index new file mode 100644 index 000000000..aaac9f967 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/ORIG_HEAD b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/ORIG_HEAD new file mode 100644 index 000000000..d1bfcf0f4 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/ORIG_HEAD @@ -0,0 +1 @@ +ae39c77c70cb6bad18bb471912460c4e1ba0f586 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/config b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/config new file mode 100644 index 000000000..575cc8599 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/config @@ -0,0 +1,15 @@ +[core] + repositoryformatversion = 0 + filemode = false + bare = false + logallrefupdates = true + worktree = ../../../submodule + symlinks = false + ignorecase = true + hideDotFiles = dotGitOnly +[remote "origin"] + url = c:/Temp/TestRepos/submodule + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/index b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/index new file mode 100644 index 000000000..e948afb27 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/index differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/info/exclude b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e new file mode 100644 index 000000000..fcf1c6381 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/18/fae1354bba0a5f1e6a531f9988369142c24a9e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d new file mode 100644 index 000000000..aa9fc5006 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/29/7aa6cd028b3336c7802c7a6f49143da4e1602d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996 new file mode 100644 index 000000000..bc9a32ebc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/38/6c80dc813b89d719797668f40c1be0a6efa996 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8 new file mode 100644 index 000000000..65a8d759f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ab/435a147bae6d5906ecfd0916a570c4ab3eeea8 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc new file mode 100644 index 000000000..49e1aafeb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ad/16e0a7684ea95bf892980a2ee412293ae979cc differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586 new file mode 100644 index 000000000..6ceffdd4e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/ae/39c77c70cb6bad18bb471912460c4e1ba0f586 @@ -0,0 +1,2 @@ +x !@=S hf%x! ]k/{k-a] xWs =,lP +#g0KIC ,51 9;alB=>|h}_{{Me?u6">:叄ʃ6^Kd \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190 new file mode 100644 index 000000000..14781032f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/c2/0765f6e24e8bbb63a648d0d11d84da63170190 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932 new file mode 100644 index 000000000..8df72a45c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/modules/submodule/objects/d3/d806a4bef96889117fd7ebac0e3cb5ec152932 @@ -0,0 +1,3 @@ +xA +0E]semDx$҂@gpQs^+ZD[a +,cGsBO# vhGpIZ4U{^c]zo@ǎ\M-\ ZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jvn~JfZ&5&ؽ +gz43^2 I{| 2mg˾15ӿ,\})TC)0Xvz֛9MՅ'6b# \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe new file mode 100644 index 000000000..d79dc30ba Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/8541fc37114bfc1dddf6bd6bffc7fae5c2e6fe differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c new file mode 100644 index 000000000..7b4b152f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/05/f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/a759da919f737221791d542f176ab49c88837f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/a759da919f737221791d542f176ab49c88837f new file mode 100644 index 000000000..a34b6c235 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/a759da919f737221791d542f176ab49c88837f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/c514b04698e068892b31c8d352b85813b99c6e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/c514b04698e068892b31c8d352b85813b99c6e new file mode 100644 index 000000000..23ab92171 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/07/c514b04698e068892b31c8d352b85813b99c6e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 new file mode 100644 index 000000000..bf5b0fcc5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/055301463b7f2f8ee5d368f8ed5c0a40ad8515 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc new file mode 100644 index 000000000..9fb640dd5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/17bb159596aea4d295f4857da77e8f96b3c7dc differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 new file mode 100644 index 000000000..b709cf461 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/2ce8682d7f3a2a3a769a6daca58950168ba5c4 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 new file mode 100644 index 000000000..ae13207d7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/3bebf072dd4bbba88833667d6ffe454df199e1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 new file mode 100644 index 000000000..5f4b4dab1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/09/768bed22680cdb0859683fa9677ccc8d5a25c1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 new file mode 100644 index 000000000..d5377341a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0a/75d9aac1dc84fb5aa51f7325c0ab53242ddef7 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 new file mode 100644 index 000000000..40f628f89 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0c/fd6c54ef6532d862408f562309dc9c74a401e8 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/52e3a556e189ba0948ae56780918011c1b167d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/52e3a556e189ba0948ae56780918011c1b167d new file mode 100644 index 000000000..4b633e504 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/52e3a556e189ba0948ae56780918011c1b167d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 new file mode 100644 index 000000000..4cbc18e84 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0d/872f8e871a30208305978ecbf9e66d864f1638 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0e/c5f433959cd46177f745903353efb5be08d151 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0e/c5f433959cd46177f745903353efb5be08d151 new file mode 100644 index 000000000..1bee56c14 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0e/c5f433959cd46177f745903353efb5be08d151 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3 new file mode 100644 index 000000000..d0ca42dad Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/0f/3fc5dddc8964b9ac1040d0e957f9eb02d9efb3 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00 new file mode 100644 index 000000000..90e729f6d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/aeee27ac45a8402c2fd5b875d66dd844e5df00 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 new file mode 100644 index 000000000..857b23686 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/deab00b2d3a6f5a3073988ac050c2d7b6655e2 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa new file mode 100644 index 000000000..6555194cb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/11/f4f3c08b737f5fd896cbefa1425ee63b21b2fa @@ -0,0 +1 @@ +xQ D\fw)c^` ۴-Q/ơdb^ץjEDC$u> , z@8qjk<٩G>z2Lva2)Veŏ:%˜{A|Ǽ5K@mg9jY _;n,YyP \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e new file mode 100644 index 000000000..4e4e175e8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/13/d1be4ea52a6ced1d7a1d832f0ee3c399348e5e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/14/39088f509b79b1535b64193137d3ce4b240734 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/14/39088f509b79b1535b64193137d3ce4b240734 new file mode 100644 index 000000000..51ddf6dcb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/14/39088f509b79b1535b64193137d3ce4b240734 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c new file mode 100644 index 000000000..064423d0c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/15/8dc7bedb202f5b26502bf3574faa7f4238d56c @@ -0,0 +1,2 @@ +xK!D]sCboi2. bK*Eep73UӾ*NYYIԔ)jL:8<{NޓH6iDC"mqH!9Tm9>R^i.= +G'+~@@j+7أENsFt]7bN) \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 new file mode 100644 index 000000000..82d65253b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/16/f825815cfd20a07a75c71554e82d8eede0b061 @@ -0,0 +1 @@ +xK!D]sObo hJqo6AJـT1h3'Lՠ.{ec,a`ZJT1#e+هJUi">\+ ץG_X6IvN;^bYgGMM \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/17/8940b450f238a56c0d75b7955cb57b38191982 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/17/8940b450f238a56c0d75b7955cb57b38191982 new file mode 100644 index 000000000..94e571e65 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/17/8940b450f238a56c0d75b7955cb57b38191982 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 new file mode 100644 index 000000000..1c4010d04 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/3310e30fb1499af8c619108ffea4d300b5e778 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 new file mode 100644 index 000000000..30f3110f1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/18/cb316b1cefa0f8a6946f0e201a8e1a6f845ab9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 new file mode 100644 index 000000000..e34ccb855 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/19/b7ac485269b672a101060894de3ba9c2a24dd1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e new file mode 100644 index 000000000..6039df00e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1a/010b1c0f081b2e8901d55307a15c29ff30af0e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85 new file mode 100644 index 000000000..9a21e26c0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/51d885170f57a0c4e8c69ff6363d91a5b51f85 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b new file mode 100644 index 000000000..30802bcec Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1c/ff9ec6a47a537380dedfdd17c9e76d74259a2b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 new file mode 100644 index 000000000..5183b8360 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1e/4ff029aee68d0d69ef9eb6efa6cbf1ec732f99 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 new file mode 100644 index 000000000..970855675 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/1f/81433e3161efbf250576c58fede7f6b836f3d3 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 new file mode 100644 index 000000000..a843890c0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/20/91d94c8bd3eb0835dc5220de5e8bb310fa1513 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/21/671e290278286fb2ce4c63d01699b67adce331 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/21/671e290278286fb2ce4c63d01699b67adce331 new file mode 100644 index 000000000..b656d0001 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/21/671e290278286fb2ce4c63d01699b67adce331 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c new file mode 100644 index 000000000..3bb19bb77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/22/7792b52aaa0b238bea00ec7e509b02623f168c differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 new file mode 100644 index 000000000..d0c8c9e1d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/3c0919c998ed110a4b6ff36f353aec8b713487 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b new file mode 100644 index 000000000..86127a344 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/92a2dacc9efb562b8635d6579fb458751c7c5b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba new file mode 100644 index 000000000..06dee3b23 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/23/ed141a6ae1e798b2f721afedbe947c119111ba differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/1a1005cd9b980732741b74385b891142bcba28 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/1a1005cd9b980732741b74385b891142bcba28 new file mode 100644 index 000000000..9b65f666f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/1a1005cd9b980732741b74385b891142bcba28 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d new file mode 100644 index 000000000..74a01373f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/2591eb280ee9eeb2ce63524b9a8b9bc4cb515d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/90b9f1a079420870027deefb49f51d6656cf74 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/90b9f1a079420870027deefb49f51d6656cf74 new file mode 100644 index 000000000..60497caa5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/24/90b9f1a079420870027deefb49f51d6656cf74 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 new file mode 100644 index 000000000..2bae66998 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/9d08ca43af9200e9ea9a098e44a5a350ebd9b3 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 new file mode 100644 index 000000000..185214727 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/25/c40b7660c08c8fb581f770312f41b9b03119d1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 new file mode 100644 index 000000000..4fcaa07e2 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/26/153a3ff3649b6c2bb652d3f06878c6e0a172f9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 new file mode 100644 index 000000000..08e61f844 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/133da702ba3c60af2a01e96c2555ff4045d692 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054 new file mode 100644 index 000000000..c7afad2a8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/27/4bbe983022fb4c02f8a2bf2ebe8da4fe130054 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add new file mode 100644 index 000000000..a95f926f8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/0de5dc27505dcdd83a75c8bf1fcd9462cd7add differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 new file mode 100644 index 000000000..d24231eda Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/5f1f181ee3b58ea751f5dd5d8f9b445520a136 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 new file mode 100644 index 000000000..d10ca636b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/d0a343aeef7a2cf0d158478966a6e587ff3863 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a new file mode 100644 index 000000000..c86edfb68 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2b/fdd7e1b6c6ae993f23dfe8e84a8e06a772fa2a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f new file mode 100644 index 000000000..83253f81c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2d/a538570bc1e5b2c3e855bf702f35248ad0735f @@ -0,0 +1,2 @@ +xK +1D]N"n{t:L$ UEQ>~7:L D [5ɇ,y2eT@z*.([žunum_|Št@ +apg%haJYծA8թ훠fN4;h[%cOuJWyΏ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 new file mode 100644 index 000000000..1d9f226e2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/2f/598248eeccfc27e5ca44d9d96383f6dfea7b16 @@ -0,0 +1 @@ +x+)JMU067c040031QH,-M-JOMLI+(aH:,:C: o>ZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jvQjn~13zדm9Wu]:$I{| 2mg˾15ӿ,\})TC)0Pavz֛9MՅ'6b \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/68dca1a561889b045a6441909f4c56145e666d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/68dca1a561889b045a6441909f4c56145e666d new file mode 100644 index 000000000..2de1c5a79 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/68dca1a561889b045a6441909f4c56145e666d @@ -0,0 +1,2 @@ +xQ +0D)rJMxMHz}xfރaRYipkUD $1fQ2q-=Y3R76ġg9e7 bw GJe*˽ |ůSY"5&Нƨng9Z3_;kdO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 new file mode 100644 index 000000000..5ec5acb59 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/31/d5472536041a83d986829240bbbdc897c6f8a6 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b new file mode 100644 index 000000000..d36138d79 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/32/21dd512b7e2dc4b5bd03046df6c81b2ab2070b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/46d64325b39e5323733492cd55f808994a2475 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/46d64325b39e5323733492cd55f808994a2475 new file mode 100644 index 000000000..11546cea4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/46d64325b39e5323733492cd55f808994a2475 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 new file mode 100644 index 000000000..061a031b6 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/33/d500f588fbbe65901d82b4e6b008e549064be0 @@ -0,0 +1,2 @@ +xA E]s +.hbo.Z x}[ ~kCA<:km`d̑d,!:𦐳P1P qHccHEO[zsK>y>隿ïm6*Rn>O \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e new file mode 100644 index 000000000..fd61b6ce5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/8dcd41e2b467991578e92bedd16971b877ef1e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 new file mode 100644 index 000000000..c653cec50 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/34/bfafff88eaf118402b44e6f3e2dbbf1a582b05 @@ -0,0 +1 @@ +xKj1D) >`7A. $<`Morlm4G&dVd[j2JCъgu_Gu%2:3XزQ'";?wpkm׾&Pf! %QJ%:Cez=6q;iO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 new file mode 100644 index 000000000..2eee60233 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/0c6eb3010efc403a6bed682332635314e9ed58 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 new file mode 100644 index 000000000..ea024ccd9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/411bfb77cd2cc431f3a03a2b4976ed94b5d241 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 new file mode 100644 index 000000000..1dd13c44a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/4704d3613ad4228e4786fc76656b11e98236c4 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/632e43612c06a3ea924bfbacd48333da874c29 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/632e43612c06a3ea924bfbacd48333da874c29 new file mode 100644 index 000000000..be7684f19 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/632e43612c06a3ea924bfbacd48333da874c29 @@ -0,0 +1 @@ +xN !LdMb60^,40;iUFf+)1vB939fG(DIݸʵA$sk]l|L{Ig$m.N5y.\a/]|Ʋ@[g4< Hl?gTsˠzCP \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/75826c96a975031d2c14368529cc5c4353a8fd b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/75826c96a975031d2c14368529cc5c4353a8fd new file mode 100644 index 000000000..24e33bc41 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/35/75826c96a975031d2c14368529cc5c4353a8fd differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 new file mode 100644 index 000000000..7f8044372 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/219b49367146cb2e6a1555b5a9ebd4d0328495 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 new file mode 100644 index 000000000..90fd9651f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/36/4bbe4ce80c7bd31e6307dce77d46e3e1759fb3 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/37/48859b001c6e627e712a07951aee40afd19b41 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/37/48859b001c6e627e712a07951aee40afd19b41 new file mode 100644 index 000000000..6a0c389e4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/37/48859b001c6e627e712a07951aee40afd19b41 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced new file mode 100644 index 000000000..e95ff3a88 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/38/5c8a0f26ddf79e9041e15e17dc352ed2c4cced @@ -0,0 +1,2 @@ +x-MK +1 uSYRą6C6뛪oknYt Ep iDCddLB+8%qk +e6fHB1J4F1l \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 new file mode 100644 index 000000000..82086466f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/47b031b3e55ae11e14a05260b1c3ffd6838d55 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f new file mode 100644 index 000000000..723a9ae4c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3b/bf0bf59b20df5d5fc58b9fc1dc07be637c301f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa new file mode 100644 index 000000000..49ee15239 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f4d30382ca33fdeba9fda895a99e0891ba37aa differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b new file mode 100644 index 000000000..3b5998ca6 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/3e/f9bfe82f9635518ae89152322f3b46fd4ba25b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/40/2784a46a4a3982294231594cbeb431f506d22c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/40/2784a46a4a3982294231594cbeb431f506d22c new file mode 100644 index 000000000..a17e05d0f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/40/2784a46a4a3982294231594cbeb431f506d22c differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/41/2b32fb66137366147f1801ecc962452757d48a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/41/2b32fb66137366147f1801ecc962452757d48a new file mode 100644 index 000000000..b183dd782 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/41/2b32fb66137366147f1801ecc962452757d48a @@ -0,0 +1,2 @@ +xK +1D]IO>"nt:x}xwUxjum'뫈.9=y 6$@T8&Lhf4Aܻf0B(.K>9S< +z_f}]Z]eO:wzރP.ިaNU6O \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648 new file mode 100644 index 000000000..33ead6112 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/42/18670ab81cc219a9f94befb5c5dad90ec52648 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 new file mode 100644 index 000000000..ac86823b6 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/aafd43bea779ec74317dc361f45ae3f532a505 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 new file mode 100644 index 000000000..d9773118b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/43/c338656342227a3a3cd3aa85cbf784061f5425 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a new file mode 100644 index 000000000..2093b4410 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/45/299c1ca5e07bba1fd90843056fb559f96b1f5a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 new file mode 100644 index 000000000..c39b53aa8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/46/6daf8552b891e5c22bc58c9d7fc1a2eb8f0289 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 new file mode 100644 index 000000000..3e5f66e55 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/6dbb3e207313d1d8aaa120c6ad204bf1295e53 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 new file mode 100644 index 000000000..d9e250e66 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/47/8172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 new file mode 100644 index 000000000..e2c49f5c4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/130a28ef567af9a6a6104c38773fedfa5f9742 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 new file mode 100644 index 000000000..9c7e471dd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/9df817155e4bdd3c6ee192a72c52f481818230 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6 new file mode 100644 index 000000000..d808d9fd9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/49/fd9edac79d15c8fbfca2d481cbb900beba22a6 @@ -0,0 +1,3 @@ +xU +0D=+ +f5dI~Ehe3x2?ذ$Ɂ%+"SRAWRm Kn\t XZ/hMƱߙg2j># \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 new file mode 100644 index 000000000..6ec674adc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4a/9550ebcc97ce22b22f45af7b829bb030d003f5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 new file mode 100644 index 000000000..1a4072794 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/253da36a0ae8bfce63aeabd8c5b58429925594 @@ -0,0 +1,2 @@ +x A +0 @AAILm l׹vGx#63tW B6%h \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 new file mode 100644 index 000000000..328c8506e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/48deed3a433909bfd6b6ab3d4b91348b6af464 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 new file mode 100644 index 000000000..adf64119a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 new file mode 100644 index 000000000..6b8c85e2b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/9fac0707f8d4195037ae5a681aa48626491541 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 new file mode 100644 index 000000000..15cb7f29a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4c/a408a8c88655f7586a1b580be6fad138121e98 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed new file mode 100644 index 000000000..57f7eb68c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/0d9401aee78eb345a8685a859d37c8c3c0bbed differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 new file mode 100644 index 000000000..53168a038 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/886e602529caa9ab11d71f86634bd1b6e0de10 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 new file mode 100644 index 000000000..f4ec0efec Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4e/b04c9e79e88f6640d01ff5b25ca2a60764f216 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 new file mode 100644 index 000000000..67dc6842f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/4f/e93c0ec83eb6305cbace3dace88ecee1b63cb6 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 new file mode 100644 index 000000000..d629a23a1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/12fd565b1393bdfda1805d4ec38ce6619e1fd1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/4f75ac95a71ef98051817618576a68505b92f9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/4f75ac95a71ef98051817618576a68505b92f9 new file mode 100644 index 000000000..1b24c721a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/4f75ac95a71ef98051817618576a68505b92f9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 new file mode 100644 index 000000000..84c9987ce Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/84fc2a88b6bdba8db93bd3953a8f4fdb470238 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 new file mode 100644 index 000000000..e2f9f67fd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/50/ce7d7d01217679e26c55939eef119e0c93e272 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/51/95a1b480f66691b667f10a9e41e70115a78351 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/51/95a1b480f66691b667f10a9e41e70115a78351 new file mode 100644 index 000000000..088ee5498 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/51/95a1b480f66691b667f10a9e41e70115a78351 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 new file mode 100644 index 000000000..6522209bd --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/52/d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 @@ -0,0 +1,3 @@ +x] +0})rnD|^`Ђm$FUo3ä,sӽ]" #b"1 9cThS//SYe'+~mrh\cQwFMQϙ]b5M R \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a new file mode 100644 index 000000000..08cb0b66f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/53/825f41ac8d640612f9423a2f03a69f3d96809a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae new file mode 100644 index 000000000..4a2415339 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/269b3f6ec3d7d4ede24dd350dd5d605495c3ae @@ -0,0 +1,2 @@ +xK +1D]I7 LӌL$FxwUAQj m'؍^v9 d- Ɯ \ ϽC'&`"Ĺ(֡9_sg}]Z}UF?\I&@mt;;ʟ3hzN \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 new file mode 100644 index 000000000..178b833e8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/59c89aa0026d543ce8343bd89871bce543f9c2 @@ -0,0 +1,3 @@ +x !D +,,,,9O bl؁3%mY.C[LEtd`\ %aBH%TvB G%Qphq]nCgP3B(H14yS)W;IVsT^܋>myJ?(_kܖ6-$#-Zzvȟ3 +:NqMB \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/7607c690372fe81fab8e3bb44c530e129118fd b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/7607c690372fe81fab8e3bb44c530e129118fd new file mode 100644 index 000000000..dccd22006 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/54/7607c690372fe81fab8e3bb44c530e129118fd differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f new file mode 100644 index 000000000..fb157a214 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/55/b4e4687e7a0d9ca367016ed930f385d4022e6f @@ -0,0 +1 @@ +xQj0DSZvJ \N 7㷱d-{LX' vm*{Z`$U9-TN{,}Kyuۣ78A_Sv.EQgSsxZZX MNRi \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 new file mode 100644 index 000000000..a8855ae67 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/6ab53c220a2eafc1212af1a024513230280ab9 @@ -0,0 +1,3 @@ +x !D +,˱1؁ ,LD bl؁3%Ihft ١XvY`L2М՝܆NsIbRЧL3Ra$Is,S~qh7~QvՃ6!-Zzvȟ3 +:9M& \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c new file mode 100644 index 000000000..36289bf7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/56/a638b76b75e068590ac999c2f8621e7f3e264c @@ -0,0 +1 @@ +xAj!Eu vuWB6s%6qΐ$^Rb[{=coj'|褯UjeKLnn5СPY|2`zzQ{ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/57/079a46233ae2b6df62e9ade71c4948512abefb b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/57/079a46233ae2b6df62e9ade71c4948512abefb new file mode 100644 index 000000000..c7eabc46b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/57/079a46233ae2b6df62e9ade71c4948512abefb differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 new file mode 100644 index 000000000..f6b2a2bfe Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/43febcb23480df0b5edb22a21c59c772bb8e29 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7 new file mode 100644 index 000000000..550d288d4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/87a5e516c53bd58efb0f02ec6aa031b6fe9ad7 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/e853f66699fd02629fd50bde08082bc005933a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/e853f66699fd02629fd50bde08082bc005933a new file mode 100644 index 000000000..cf6db633c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/58/e853f66699fd02629fd50bde08082bc005933a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/59/6803b523203a4851c824c07366906f8353f4ad b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/59/6803b523203a4851c824c07366906f8353f4ad new file mode 100644 index 000000000..cbc8cbef3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/59/6803b523203a4851c824c07366906f8353f4ad differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 new file mode 100644 index 000000000..7b41413da Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/2411f8075f48a6b2fdb85ebc0d371747c4df15 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d new file mode 100644 index 000000000..63c86bd33 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/341ead2ba6f2af98ce5ec3fe84f6b6d2899c0d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 new file mode 100644 index 000000000..541001456 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5c/3b68a71fc4fa5d362fd3875e53137c6a5ab7a5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/c1018e90b19654bee986b7a0c268804d39659d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/c1018e90b19654bee986b7a0c268804d39659d new file mode 100644 index 000000000..7500b9914 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/c1018e90b19654bee986b7a0c268804d39659d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f new file mode 100644 index 000000000..9d8691eb2 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5d/dd0fe66f990dc0e5cf9fec6d9b465240e9537f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d new file mode 100644 index 000000000..aca2666cf Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5e/b7bb6a146eb3c7fd3990b240a2308eceb1cf8d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 new file mode 100644 index 000000000..aec3867c8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/5f/bfbdc04b4eca46f54f4853a3c5a1dce28f5165 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf new file mode 100644 index 000000000..3f266f6df Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/61fe116ecba0800c26113ea1a7dfac2e16eeaf differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 new file mode 100644 index 000000000..fa63afba1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/60/91fc2c036a382a69489e3f518ee5aae9a4e567 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb new file mode 100644 index 000000000..e830cafe5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/340eeed7340fa6a8792def9a5938bb5d4434bb differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 new file mode 100644 index 000000000..bedc5f27e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/61/78885b38fe96e825ac0f492c0a941f288b37f6 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b new file mode 100644 index 000000000..b6f0607bb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/12c31dab5e482247d7977e4f0dd3601decf13b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/269111c3b02a9355badcb9da8678b1bf41787b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/269111c3b02a9355badcb9da8678b1bf41787b new file mode 100644 index 000000000..0edf65994 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/269111c3b02a9355badcb9da8678b1bf41787b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/33c6a0670228627f93c01cef32485a30403670 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/33c6a0670228627f93c01cef32485a30403670 new file mode 100644 index 000000000..81428dd62 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/33c6a0670228627f93c01cef32485a30403670 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a new file mode 100644 index 000000000..c0f822d2c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/62/c4f6533c9a3894191fdcb96a3be935ade63f1a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/63/247125386de9ec90a27ad36169307bf8a11a38 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/63/247125386de9ec90a27ad36169307bf8a11a38 new file mode 100644 index 000000000..bc2d7384d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/63/247125386de9ec90a27ad36169307bf8a11a38 @@ -0,0 +1 @@ +xݏ;1 D}AV\8HIVp|?LyOuN7C] ͥlt:iA(xip,O;o7 UYZ Bý]dUmyk[cͥ)!X{Z \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/110d77886b2af6309b9212961e72b8583e5fa9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/110d77886b2af6309b9212961e72b8583e5fa9 new file mode 100644 index 000000000..877bad703 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/110d77886b2af6309b9212961e72b8583e5fa9 @@ -0,0 +1 @@ +x=N1 ^r !J:.`'lP^gT343Ҕup*Z %l4irHz,곥[M]aJҐb5l8OX$XճEa")U$d2zODŽų>m'qZ渍O`lFO1!n'=-]A&e˯^o^ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 new file mode 100644 index 000000000..ffda698f0 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/67/18a45909532d1fcf5600d0877f7fe7e78f0b86 @@ -0,0 +1 @@ +xM1 DNi`ǹDB~ǧ]ݠY74M8J?$_k[Q,"Zz˟39 LO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/63fce0130db092936b137cabd693ec234eb060 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/63fce0130db092936b137cabd693ec234eb060 new file mode 100644 index 000000000..f578a4a68 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/63fce0130db092936b137cabd693ec234eb060 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 new file mode 100644 index 000000000..4d41ad8cd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/76/ab0e2868197ec158ddd6c78d8a0d2fd73d38f9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 new file mode 100644 index 000000000..09f1e4d3a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/a3edf2bcfee22398e6b55295aa56366b7aaf76 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9 new file mode 100644 index 000000000..b4c4ef734 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7a/f14d9c679baaef35555095f4f5d33e9a569ab9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/04ca611203ed320c5f495b9813054dd23be3be b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/04ca611203ed320c5f495b9813054dd23be3be new file mode 100644 index 000000000..e3ba6056d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/04ca611203ed320c5f495b9813054dd23be3be @@ -0,0 +1,2 @@ +xQ D{ -,tc^`%b(K|ͼd&k)Dsl<f4a1B8zsCvŘEQdO>E񧯵#}%xu z{yV%rɐdžp֨tDŽ +UrL \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 new file mode 100644 index 000000000..52fde92a1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/2c5228c9e90170d4a35e6558e47163daf092e5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/b63eed597130ba4abb87b3e544b85021905520 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/b63eed597130ba4abb87b3e544b85021905520 new file mode 100644 index 000000000..769f29c6e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7c/b63eed597130ba4abb87b3e544b85021905520 @@ -0,0 +1,3 @@ +xK +1D]t'"nH:L$FxwUAQܖ6f7IT*zJ +1#;@rX]ꞺC3A F'aj#Tf acn]_+s[mG'+~m9i PFCQge"N \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 new file mode 100644 index 000000000..d12d7b4a7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7e/2d058d5fedf8329db44db4fac610d6b1a89159 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7f/7a2da58126226986d71c6ddfab4afba693280d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7f/7a2da58126226986d71c6ddfab4afba693280d new file mode 100644 index 000000000..2f833c292 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/7f/7a2da58126226986d71c6ddfab4afba693280d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b new file mode 100644 index 000000000..3daf6c3e0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/80/a8fbb3abb1ba423d554e9630b8fc2e5698f86b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551 new file mode 100644 index 000000000..6d8702404 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/1c70fcb6d5bbd022d04cc31836d30b436f9551 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/87117062b750eed4f93fd7e899f17b52ce554d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/87117062b750eed4f93fd7e899f17b52ce554d new file mode 100644 index 000000000..19cac9faf Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/81/87117062b750eed4f93fd7e899f17b52ce554d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e new file mode 100644 index 000000000..5a96a4e4e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/07d93a155903a5c49576583f0ce1f6ff897c0e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19 new file mode 100644 index 000000000..99f828649 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/6b8b82b26cab22eaaed8820877c76d6c8bca19 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e new file mode 100644 index 000000000..066190fb8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/83/824a8c6658768e2013905219cc8c64cc3d9a2e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/84/9619b03ae540acee4d1edec96b86993da6b497 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/84/9619b03ae540acee4d1edec96b86993da6b497 new file mode 100644 index 000000000..67271ac50 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/84/9619b03ae540acee4d1edec96b86993da6b497 @@ -0,0 +1,3 @@ +xK +1D]v7t3L$ UEVZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jv^j9!Ɖ9%`<sBާHrS3d Ң2 wI{| 2mg˾15ӿ,\})TC)00avʉz֛9MՅ'6bG \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f new file mode 100644 index 000000000..4ec013881 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/095d8fd01594f4d14454d073e3ac57b9ce485f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a new file mode 100644 index 000000000..f4249c23d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/5b53cb2aa9ceb1139f5312fcfa3cc3c5a47c9a @@ -0,0 +1 @@ +x퐱 0 S{"2d,0^?&SH[8눪E`фrZ*drl, cbF/'gв \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a new file mode 100644 index 000000000..790750c0f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/7cd60d49ce3a1a770ece43b7d29b5cf462a33a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 new file mode 100644 index 000000000..a90ee08ce Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8b/fb012a6d809e499bd8d3e194a3929bc8995b93 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 new file mode 100644 index 000000000..e42393cf7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8c/749d9968d4b10dcfb06c9f97d0e5d92d337071 @@ -0,0 +1,2 @@ +xAB!C]s +.acxf`|_ bh5m^mzL`}$26#"8`s.`ԝܺ.!bH\< i",K8ٗ_X>MeЏ:7]AC40뭙Q]Q\.,VO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa new file mode 100644 index 000000000..d2de777cc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/8f/4433f8593ddd65b7dd43dd4564d841f4d9c8aa differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 new file mode 100644 index 000000000..35453ebfd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/90/a336c7dacbe295159413559b0043b8bdc60d57 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 new file mode 100644 index 000000000..d5df393e9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/2b2d7819cf9c1029e414883857ed61d597a1a5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c new file mode 100644 index 000000000..c214ab206 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/8bb3e09090a9995d48af9a2a6296d7e6088d1c differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1 new file mode 100644 index 000000000..51a456f42 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/91/f44111cb1cb1358ac6944ad356ca1738813ea1 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 new file mode 100644 index 000000000..b6b92c842 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/92/7d4943cdbdc9a667db8e62cfd0a41870235c51 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 new file mode 100644 index 000000000..4b2d93b07 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/93/77fccdb210540b8c0520cc6e80eb632c20bd25 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a new file mode 100644 index 000000000..143093831 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/94/4f5dd1a867cab4c2bbcb896493435cae1dcc1a @@ -0,0 +1,2 @@ +xK!D]s +.z7 |2. bhWKVmH0~7z"P9`:Qi)QLEyq=oC*P6-"4l0StAHZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jv^jnb^nJfZZjQj^ X#3|>^U:'A2R2 I{| 2mg˾15ӿ,\})TC)0H!vʉz֛9MՅ'6bGx \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 new file mode 100644 index 000000000..91113ee8e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/a9/0bc3fb6f15181972a2959a921429efbd81a473 @@ -0,0 +1,2 @@ +xK +1D]};7d=oo^UQT\;hk6@g 5rѓ]uOMndgz&c圈'} NJ7p?(G\8CآGTg9x$faxN" \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 new file mode 100644 index 000000000..7da1da656 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/40af3cb8a3ed2e2843e96d9aa7871336b94573 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b new file mode 100644 index 000000000..d840c1a57 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/6c44a2e84492ad4b41bb6bac87353e9d02ac8b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 new file mode 100644 index 000000000..8840d00c5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ab/929391ac42572f92110f3deeb4f0844a951e22 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 new file mode 100644 index 000000000..4c32d63f8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ac/4045f965119e6998f4340ed0f411decfb3ec05 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730 new file mode 100644 index 000000000..ae3ef8ce3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/01aebfdf2ac13145efafe3f9fcf798882f1730 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/26b598134264fd284292cb233fc0b2f25851da b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/26b598134264fd284292cb233fc0b2f25851da new file mode 100644 index 000000000..5819a2e25 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/26b598134264fd284292cb233fc0b2f25851da differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a new file mode 100644 index 000000000..71023de39 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a14492498136771f69dd451866cabcb0e9ef9a differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 new file mode 100644 index 000000000..3091b8f3d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ad/a55a45d14527dc3dfc714ea1c65d2e1e6fbe87 @@ -0,0 +1 @@ +x+)JMU067d040031QH,-M-JOMLI+(aH:,:C: o>ZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jvn~JfZ&5`nלU7 V.6t6L/R2 I{| 2mg˾15ӿ,\})TC)0<vʉz֛9MՅ'6bN* \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 new file mode 100644 index 000000000..20fa838f2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b2/d399ae15224e1d58066e3c8df70ce37de7a656 @@ -0,0 +1,2 @@ +xQA1+xċϡ-kI*5f/z af!^/WJcܤ5Lƛ;+B6HZP|`h>\($sX@75}57K ++= ;g @!4!,\$\ \b/Hs#aQ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 new file mode 100644 index 000000000..2820b46cc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b4/2712cfe99a1a500b2a51fe984e0b8a7702ba11 @@ -0,0 +1,5 @@ +xA + {B{M1 ߯>P3F֎7E02 X0̒,)$;:ܷ(: \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 new file mode 100644 index 000000000..fb102f15d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b6/f610aef53bd343e6c96227de874c66f00ee8e8 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 new file mode 100644 index 000000000..22f2d137d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b7/a2576f9fc20024ac9ef17cb134acbd1ac73127 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d new file mode 100644 index 000000000..24f029900 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/b8/a3a806d3950e8c0a03a34f234a92eff0e2c68d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 new file mode 100644 index 000000000..f35586f7f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ba/cac9b3493509aa15e1730e1545fc0919d1dae0 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 new file mode 100644 index 000000000..0d4bdb323 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/744705e1d8a019993cf88f62bc4020f1b80919 @@ -0,0 +1,2 @@ +xK +1D]}%%tYH& UJuj7:P(#F̄ģ1+k#vΚS8W|٨%Kpɯ3\Vv#MQg?wH@(c s9t 嶭{kO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 new file mode 100644 index 000000000..436d5a076 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bc/95c75d59386147d1e79a87c33068d8dbfd71f2 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 new file mode 100644 index 000000000..75ab1f0f3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/593285fc7fe4ca18ccdbabf027f5d689101452 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 new file mode 100644 index 000000000..0f7421963 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/867fbae2faa80b920b002b80b1c91bcade7784 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 new file mode 100644 index 000000000..2aafdc64f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/bd/9cb4cd0a770cb9adcb5fce212142ef40ea1c35 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/be/f6e37b3ee632ba74159168836f382fed21d77d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/be/f6e37b3ee632ba74159168836f382fed21d77d new file mode 100644 index 000000000..6c243150d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/be/f6e37b3ee632ba74159168836f382fed21d77d @@ -0,0 +1,2 @@ +xK +1D]}3?Ač7p?$=`Ґx}Gރ`)+0ѮUh.NWޓ!Idlj-f 9UV61:̸ !>Z.P0x hhQ+t`1NZe,X[ =vyI_vJ^2$?I7o4{K>V!~|U= \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c new file mode 100644 index 000000000..2f2ada732 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c5/bbe550b9f09444bdddd3ecf3d97c0b42aa786c differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec new file mode 100644 index 000000000..475b87ef9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/07fc30883e335def28cd686b51f6cfa02b06ec @@ -0,0 +1,2 @@ +xQ +1 D)r%n "x/m[[oo{0k)iכ*`ZavJ>,af<EZȳ5%<'.v,;]=2tws-,w8@ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed new file mode 100644 index 000000000..ae430bd4a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c6/92ecf62007c0ac9fb26e2aa884de2933de15ed differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d new file mode 100644 index 000000000..5dae4c3ac Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c8/f06f2e3bb2964174677e91f0abead0e43c9e5d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb new file mode 100644 index 000000000..da8dba244 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/174cef549ec94ecbc43ef03cdc775b4950becb @@ -0,0 +1,2 @@ +xQ +0D)rfnSxfCHx}xfc,˵Y kUb8pu`%|@r3GtB;W]!z'%QiӐdT ?\=d/sYe';^r#l`6m Z7U^e6oVO \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa new file mode 100644 index 000000000..fd1ec9fab Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/c9/4b27e41064c521120627e07e2035cca1d24ffa differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b new file mode 100644 index 000000000..32ba2aa53 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/b2cf23998b40f1af2d9d9a756dc9e285a8df4b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6 new file mode 100644 index 000000000..6d0f60077 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ca/ff6b7d44973f53e3e0cf31d0d695188b19aec6 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 new file mode 100644 index 000000000..cf9cd7d39 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/491780d82e46dc88a065b965ab307a038f2bc2 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 new file mode 100644 index 000000000..e11181a96 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cb/6693a788715b82440a54e0eacd19ba9f6ec559 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1 new file mode 100644 index 000000000..85b3b8112 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/338e4710c9b257106b8d16d82f86458d5beaf1 @@ -0,0 +1,2 @@ +xK!]s3`bo1 gpWEGmx]6d +eaΉ碵z.Dv [hD[JﱶwX[.2nuVƉZڳF!x88GPP_?KN \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f new file mode 100644 index 000000000..9a0cb7a0c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cc/3e3009134cb88014129fc8858d1101359e5e2f @@ -0,0 +1,2 @@ +x] +0})&_H -Fb[6}0L2wPzc*sXb Rt#G$[lvH$kf.ʧLF+ QHD|68Wl.S]uoNOu9Va0^ZF9^# Od \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 new file mode 100644 index 000000000..860f9952f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/8860d49e3bea6fd745874a01b7c3e46da8cbc3 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f new file mode 100644 index 000000000..ff0624ccb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ce/e656c392ad0557b3aae0fb411475c206e2926f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d new file mode 100644 index 000000000..36b0289e6 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/cf/8c5cc8a85a1ff5a4ba51e0bc7cf5665669924d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 new file mode 100644 index 000000000..d52a56ffe --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/7ec190c306ec690bac349e87d01c4358e49bb2 @@ -0,0 +1,2 @@ +xՏ 0 3aOb%ǑS=HT@u:]uYG%LE;u`_?g~0Ҕ. +׋PӜxvXi ӭf! \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb new file mode 100644 index 000000000..5f7e286ff Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d0/d4594e16f2e19107e3fa7ea63e7aaaff305ffb differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e new file mode 100644 index 000000000..558a8513f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d2/f8637f2eab2507a1e13cbc9df4729ec386627e differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/3cedf513c059e0515653fa2c2e386631387a05 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/3cedf513c059e0515653fa2c2e386631387a05 new file mode 100644 index 000000000..d6d4c2b45 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/3cedf513c059e0515653fa2c2e386631387a05 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 new file mode 100644 index 000000000..930bf5a5e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/719a5ae8e4d92276b5313ce976f6ee5af2b436 @@ -0,0 +1,2 @@ +x=10 E}uAHب zRHPT Brh/]?a 48,_MdkуTPF!TZQ? +R֧FN_J͆ h{(kLKV1p+Q A \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 new file mode 100644 index 000000000..5902e0f32 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7aa3bbfe1c0c49b909781251b956dbabe85f96 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b new file mode 100644 index 000000000..b2f39bff4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d3/7ad72a2052685fc6201c2af90103ad42d2079b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/207f77243500bec335ab477f9227fcdb1e271a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/207f77243500bec335ab477f9227fcdb1e271a new file mode 100644 index 000000000..862e4e5bc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/207f77243500bec335ab477f9227fcdb1e271a @@ -0,0 +1,2 @@ +xK +1D]O'7t:݌H&z:]oZBBXl(昭+d<"6^% A( J,% %5SSmR^؊Nu^뢏O:Wځ| DcFQEn6#Q \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 new file mode 100644 index 000000000..0b3611ae4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d4/27e0b2e138501a3d15cc376077a3631e15bd46 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 new file mode 100644 index 000000000..7d73449eb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/093787ef302b941b6aab081b99fb4880038bd8 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f new file mode 100644 index 000000000..a7921de43 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/a61b0b4992a4f0caa887fa08b52431e727bb6f differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 new file mode 100644 index 000000000..924bdbbb5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/b6fc965c926a1bfc9ee456042b94088b5c5d21 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 new file mode 100644 index 000000000..0d2534bc9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d5/ec1152fe25e9fec00189eb00b3db71db24c218 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 new file mode 100644 index 000000000..1671f9f2c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/42b9770c66bba94a08df09b5efb095001f76d7 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 new file mode 100644 index 000000000..baae3f0e0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/462fa3f5292857db599c54aea2bf91616230c5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 new file mode 100644 index 000000000..8f9ae1fc6 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d6/cf6c7741b3316826af1314042550c97ded1d50 @@ -0,0 +1,2 @@ +xQ +1 D)r%i@oje[7̤ZʽMSLBNlm B~>-uY8ꠟt֯]Qa͠3f]>bl(A] \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460 new file mode 100644 index 000000000..b02cda4fa --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d7/308cc367b2cc23f710834ec1fd8ffbacf1b460 @@ -0,0 +1 @@ +xK @]s)Ё7 ]I(x{ ^,nлSo`/X)ٙB@GÔaD 4xwlCv?-79d,hF4Z ;ƝH}= \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/74671ef5b20184836cb983bb273e5280384d0b b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/74671ef5b20184836cb983bb273e5280384d0b new file mode 100644 index 000000000..1d8037895 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/74671ef5b20184836cb983bb273e5280384d0b differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66 new file mode 100644 index 000000000..74f807e68 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/dec75ff2f8b41d1c5bfef0cd57b7300c834f66 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 new file mode 100644 index 000000000..988145322 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d8/fa77b6833082c1ea36b7828a582d4c43882450 @@ -0,0 +1 @@ +x1 DQkN1&6%lBknaa1kdI(Ur'7LA,+Wm9 I'U͹_ܰN \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 new file mode 100644 index 000000000..5fa10405c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/d9/63979c237d08b6ba39062ee7bf64c7d34a27f8 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf new file mode 100644 index 000000000..6292118e0 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/da/178208145ef585a1bd5ca5f4c9785d738df2cf differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 new file mode 100644 index 000000000..b82e7fcaf Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/db/6261a7c65c7fd678520c9bb6f2c47582ab9ed5 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd new file mode 100644 index 000000000..55626a57b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/2ae5ab264e5592aa754235d5ad5eac8f0ecdfd differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 new file mode 100644 index 000000000..8fd60cbe8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/dd/9a570c3400e6e07bc4d7651d6e20b08926b3d9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 new file mode 100644 index 000000000..04dda4a75 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/de/872ee3618b894992e9d1e18ba2ebe256a112f9 @@ -0,0 +1 @@ +x퐱 S3ŏlKAB4Wb T5:8Sc ԻP`KIˆO3Z&ؐ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd new file mode 100644 index 000000000..e13569440 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/df/e3f22baa1f6fce5447901c3086bae368de6bdd differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e0/67f9361140f19391472df8a82d6610813c73b7 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e0/67f9361140f19391472df8a82d6610813c73b7 new file mode 100644 index 000000000..955431dd7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e0/67f9361140f19391472df8a82d6610813c73b7 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 new file mode 100644 index 000000000..751f1dd33 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/129b3cfb5898e0fbd606e0cb80b2755e50d161 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 new file mode 100644 index 000000000..4a812e5df Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e1/7ace1492648c9dc5701bad5c47af9d1b60c4e9 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 new file mode 100644 index 000000000..7b84ce966 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e2/c6abbd55fed5ac71a5f2751e29b4a34726a595 @@ -0,0 +1 @@ +x+)JMU067f040031QH,-M-JOMLI+(aH:,:C: o>ZC'g$楧f&%%g5qYeZokM2ԐX\ZDPC~^ZNfrIf^:XZHي1O(_,' jvn~JfZ&5%\N,5[e2 I{| 2mg˾15ӿ,\})TC)0Dvz֛9MՅ'6b \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 new file mode 100644 index 000000000..a28ded3fb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/1e7ad3ed298f24e383c4950f4671993ec078e4 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e new file mode 100644 index 000000000..8da234114 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e3/76fbdd06ebf021c92724da9f26f44212734e3e @@ -0,0 +1,3 @@ +xA@E]s +`@ uH)M=Scz:ʊ(N+6ޛDFe𭭘Yg$+G&F +pG 4mQ\85#FC~QERu);c6'j \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 new file mode 100644 index 000000000..870c3e732 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/9f917b448d1340b31d76e54ba388268fd4c922 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f new file mode 100644 index 000000000..c7e1ee9d7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/e4/f618a2c3ed0669308735727df5ebf2447f022f @@ -0,0 +1,2 @@ +xK!D]s +.iOboi2. bhJQ6b`7:DN.%4uIQYcm`Q¨ aQYa@>ɗEc9%bhf1x}xwUQv kv@`O;$Kșybh2癈sLA ?R\˷ץ(~Yïb-'Yǎp=Njq˟m[zO+ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 new file mode 100644 index 000000000..30e07e5b7 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/c03f2c5139618d87d53614c153823bf1f31396 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d new file mode 100644 index 000000000..16ce49a1b Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fa/da9356aa3f74622327a3038ae9c6f92e1c5c1d differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 new file mode 100644 index 000000000..4f1e72688 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fb/738a106cfd097a4acb96ce132ecb1ad6c46b03 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 new file mode 100644 index 000000000..be8a810cd Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/4c636d6515e9e261f9260dbcf3cc6eca97ea08 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 new file mode 100644 index 000000000..20493e68c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/7d7b805f7a9428574f4f802b2e34cd20ab9d99 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/90237dc4891fa6c69827fc465632225e391618 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/90237dc4891fa6c69827fc465632225e391618 new file mode 100644 index 000000000..961814bae Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fc/90237dc4891fa6c69827fc465632225e391618 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 new file mode 100644 index 000000000..21e6b2c55 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/57d2d6770fad8e9959124793a17f441b571e66 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a new file mode 100644 index 000000000..2f9d83b26 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fd/89f8cffb663ac89095a0f9764902e93ceaca6a @@ -0,0 +1,2 @@ +xK!D]s +.{`cx/ɸ`0 oURy|Y`dPA!4C2d=x#e`BgrubLffG@՗-}KԲUy=];)r0 R$(%o=׶OPw \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 new file mode 100644 index 000000000..4ce7d2297 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/fe/5407fc50a53aecb41d1a6e9ea7b612e581af87 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 new file mode 100644 index 000000000..eada39b77 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/49d07869831ad761bbdaea026086f8789bcb00 differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/b312248d607284c290023f9502eea010d34efd b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/b312248d607284c290023f9502eea010d34efd new file mode 100644 index 000000000..7e46c4fe3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/objects/ff/b312248d607284c290023f9502eea010d34efd differ diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/branch new file mode 100644 index 000000000..03f79a3dc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/branch @@ -0,0 +1 @@ +7cb63eed597130ba4abb87b3e544b85021905520 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_ancestor b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_ancestor new file mode 100644 index 000000000..4bc37ac60 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_ancestor @@ -0,0 +1 @@ +2da538570bc1e5b2c3e855bf702f35248ad0735f diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side1 new file mode 100644 index 000000000..ca6dd679d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side1 @@ -0,0 +1 @@ +a7dbfcbfc1a60709cb80b5ca24539008456531d0 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side2 new file mode 100644 index 000000000..b8160f80e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/df_side2 @@ -0,0 +1 @@ +fc90237dc4891fa6c69827fc465632225e391618 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/ff_branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/ff_branch new file mode 100644 index 000000000..e9e90512f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/ff_branch @@ -0,0 +1 @@ +fd89f8cffb663ac89095a0f9764902e93ceaca6a diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..8a329ae5f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +bd593285fc7fe4ca18ccdbabf027f5d689101452 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo1 new file mode 100644 index 000000000..4d2c66902 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo1 @@ -0,0 +1 @@ +16f825815cfd20a07a75c71554e82d8eede0b061 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo2 new file mode 100644 index 000000000..f503977a7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo2 @@ -0,0 +1 @@ +158dc7bedb202f5b26502bf3574faa7f4238d56c diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo3 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo3 new file mode 100644 index 000000000..b92994f10 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo3 @@ -0,0 +1 @@ +50ce7d7d01217679e26c55939eef119e0c93e272 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo4 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo4 new file mode 100644 index 000000000..f33d57cbc --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo4 @@ -0,0 +1 @@ +54269b3f6ec3d7d4ede24dd350dd5d605495c3ae diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo5 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo5 new file mode 100644 index 000000000..e9f943385 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo5 @@ -0,0 +1 @@ +e4f618a2c3ed0669308735727df5ebf2447f022f diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo6 new file mode 100644 index 000000000..4c5a98ad9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/octo6 @@ -0,0 +1 @@ +b6f610aef53bd343e6c96227de874c66f00ee8e8 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/previous b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/previous new file mode 100644 index 000000000..7bc1a8d15 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/previous @@ -0,0 +1 @@ +c607fc30883e335def28cd686b51f6cfa02b06ec diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ancestor b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ancestor new file mode 100644 index 000000000..4092d428f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ancestor @@ -0,0 +1 @@ +2392a2dacc9efb562b8635d6579fb458751c7c5b diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ours b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ours new file mode 100644 index 000000000..a1c50dce8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_ours @@ -0,0 +1 @@ +34bfafff88eaf118402b44e6f3e2dbbf1a582b05 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_theirs b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_theirs new file mode 100644 index 000000000..130989399 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/rename_conflict_theirs @@ -0,0 +1 @@ +a802e06f1782a9645b9851bc7202cee74a8a4972 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames1 new file mode 100644 index 000000000..3d248102c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames1 @@ -0,0 +1 @@ +412b32fb66137366147f1801ecc962452757d48a diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames2 new file mode 100644 index 000000000..d22621561 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/renames2 @@ -0,0 +1 @@ +ab40af3cb8a3ed2e2843e96d9aa7871336b94573 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules new file mode 100644 index 000000000..e5511eca9 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules @@ -0,0 +1 @@ +d8dec75ff2f8b41d1c5bfef0cd57b7300c834f66 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch new file mode 100644 index 000000000..7d47e07b8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch @@ -0,0 +1 @@ +811c70fcb6d5bbd022d04cc31836d30b436f9551 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch2 new file mode 100644 index 000000000..ced60d813 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/submodules-branch2 @@ -0,0 +1 @@ +7c04ca611203ed320c5f495b9813054dd23be3be diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10 new file mode 100644 index 000000000..5b378cd88 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10 @@ -0,0 +1 @@ +0ec5f433959cd46177f745903353efb5be08d151 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10-branch new file mode 100644 index 000000000..b3db6c892 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-10-branch @@ -0,0 +1 @@ +11f4f3c08b737f5fd896cbefa1425ee63b21b2fa diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11 new file mode 100644 index 000000000..154de9a64 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11 @@ -0,0 +1 @@ +3168dca1a561889b045a6441909f4c56145e666d diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11-branch new file mode 100644 index 000000000..2e4118029 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-11-branch @@ -0,0 +1 @@ +6718a45909532d1fcf5600d0877f7fe7e78f0b86 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13 new file mode 100644 index 000000000..297573a57 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13 @@ -0,0 +1 @@ +a3fabece9eb8748da810e1e08266fef9b7136ad4 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13-branch new file mode 100644 index 000000000..22e429a61 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-13-branch @@ -0,0 +1 @@ +05f3c1a2a56ca95c3d2ef28dc9ddf32b5cd6c91c diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14 new file mode 100644 index 000000000..89051853a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14 @@ -0,0 +1 @@ +7e2d058d5fedf8329db44db4fac610d6b1a89159 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14-branch new file mode 100644 index 000000000..0158f950c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-14-branch @@ -0,0 +1 @@ +8187117062b750eed4f93fd7e899f17b52ce554d diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt new file mode 100644 index 000000000..474074120 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt @@ -0,0 +1 @@ +566ab53c220a2eafc1212af1a024513230280ab9 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt-branch new file mode 100644 index 000000000..2f5f1a4af --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-2alt-branch @@ -0,0 +1 @@ +c9174cef549ec94ecbc43ef03cdc775b4950becb diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt new file mode 100644 index 000000000..18e50ae12 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt @@ -0,0 +1 @@ +4c9fac0707f8d4195037ae5a681aa48626491541 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt-branch new file mode 100644 index 000000000..7bc1a8d15 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-3alt-branch @@ -0,0 +1 @@ +c607fc30883e335def28cd686b51f6cfa02b06ec diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4 new file mode 100644 index 000000000..f49bbf956 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4 @@ -0,0 +1 @@ +cc3e3009134cb88014129fc8858d1101359e5e2f diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4-branch new file mode 100644 index 000000000..bff519ef1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-4-branch @@ -0,0 +1 @@ +183310e30fb1499af8c619108ffea4d300b5e778 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1 new file mode 100644 index 000000000..963a7b336 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1 @@ -0,0 +1 @@ +4fe93c0ec83eb6305cbace3dace88ecee1b63cb6 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1-branch new file mode 100644 index 000000000..4a22138e7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-1-branch @@ -0,0 +1 @@ +478172cb2f5ff9b514bc9d04d3bd5ef5840cb3b2 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2 new file mode 100644 index 000000000..aa4ada17e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2 @@ -0,0 +1 @@ +3b47b031b3e55ae11e14a05260b1c3ffd6838d55 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2-branch new file mode 100644 index 000000000..5553cdba1 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-5alt-2-branch @@ -0,0 +1 @@ +f48097eb340dc5a7cae55aabcf1faf4548aa821f diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6 new file mode 100644 index 000000000..fb685bb63 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6 @@ -0,0 +1 @@ +99b4f7e4f24470fa06b980bc21f1095c2a9425c0 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6-branch new file mode 100644 index 000000000..efc4c55ac --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-6-branch @@ -0,0 +1 @@ +a43150a738849c59376cf30bb2a68348a83c8f48 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7 new file mode 100644 index 000000000..9c9424346 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7 @@ -0,0 +1 @@ +d874671ef5b20184836cb983bb273e5280384d0b diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7-branch new file mode 100644 index 000000000..1762bb5db --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-7-branch @@ -0,0 +1 @@ +5195a1b480f66691b667f10a9e41e70115a78351 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8 new file mode 100644 index 000000000..837c4915a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8 @@ -0,0 +1 @@ +3575826c96a975031d2c14368529cc5c4353a8fd diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8-branch new file mode 100644 index 000000000..874230eff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-8-branch @@ -0,0 +1 @@ +52d8bc572af2b6d4ee0d5e62ed5d1fbad92210a9 diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9 b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9 new file mode 100644 index 000000000..b968a3efb --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9 @@ -0,0 +1 @@ +c35dee9bcc0e989f3b0c40f68372a9a51b6c4e6a diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9-branch b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9-branch new file mode 100644 index 000000000..7f3097b69 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/trivial-9-branch @@ -0,0 +1 @@ +13d1be4ea52a6ced1d7a1d832f0ee3c399348e5e diff --git a/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/unrelated b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/unrelated new file mode 100644 index 000000000..bb877be2e --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/mergerenames_wd/dot_git/refs/heads/unrelated @@ -0,0 +1 @@ +55b4e4687e7a0d9ca367016ed930f385d4022e6f 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/revert_testrepo_wd/a.txt b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/a.txt new file mode 100644 index 000000000..bc90ea420 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/a.txt @@ -0,0 +1,7 @@ +This is file a.txt +This is the 3rd revision of this file. +It +also +has +several +lines. \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/b.txt b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/b.txt new file mode 100644 index 000000000..1ca53b49d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/b.txt @@ -0,0 +1,7 @@ +This is file b.txt +This is the 3rd revision of this file. +It +also +has +several +lines. \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/c.txt b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/c.txt new file mode 100644 index 000000000..88925a548 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/c.txt @@ -0,0 +1,7 @@ +This is file c.txt +This is the 3rd revision of this file. +It +also +has +several +lines. \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/config new file mode 100644 index 000000000..78387c50b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_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/revert_testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/index new file mode 100644 index 000000000..c39bce8fb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/info/packs b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/info/packs new file mode 100644 index 000000000..59da9792c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/info/packs @@ -0,0 +1,2 @@ +P pack-29d1a036908407037b737d1cb436707551c3cedf.pack + diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.idx b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.idx new file mode 100644 index 000000000..ce6281cd8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.idx differ diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.pack b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.pack new file mode 100644 index 000000000..e1eee599a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/objects/pack/pack-29d1a036908407037b737d1cb436707551c3cedf.pack differ diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..743c907ae --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +cb4f7f0eca7a0114cdafd8537332aa17de36a4e9 diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert new file mode 100644 index 000000000..7438cbb2d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert @@ -0,0 +1 @@ +b6fbb29b625aabe0fb5736da6fd61d4147e4405e diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_merge b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_merge new file mode 100644 index 000000000..0a6cfc127 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_merge @@ -0,0 +1 @@ +2747045c29b5b9c5624225ce600f3117fdcf0b87 diff --git a/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_rename b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_rename new file mode 100644 index 000000000..c2ca6d982 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/revert_testrepo_wd/dot_git/refs/heads/revert_rename @@ -0,0 +1 @@ +c4b5cea70e4cd5b633ed0f10ae0ed5384e8190d8 diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/HEAD b/LibGit2Sharp.Tests/Resources/shallow.git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/shallow.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/config b/LibGit2Sharp.Tests/Resources/shallow.git/config new file mode 100644 index 000000000..a88b74b69 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/shallow.git/config @@ -0,0 +1,8 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = true + ignorecase = true + precomposeunicode = false +[remote "origin"] + url = file://testrepo.git diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx b/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx new file mode 100644 index 000000000..bfc7d24ff Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.idx differ diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack b/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack new file mode 100644 index 000000000..ccc6932fc Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/shallow.git/objects/pack/pack-706e49b161700946489570d96153e5be4dc31ad4.pack differ diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/packed-refs b/LibGit2Sharp.Tests/Resources/shallow.git/packed-refs new file mode 100644 index 000000000..97eed743b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/shallow.git/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +a65fedf39aefe402d3bb6e24df4d4f5fe4547750 refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/refs/.gitkeep b/LibGit2Sharp.Tests/Resources/shallow.git/refs/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/LibGit2Sharp.Tests/Resources/shallow.git/shallow b/LibGit2Sharp.Tests/Resources/shallow.git/shallow new file mode 100644 index 000000000..9536ad89c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/shallow.git/shallow @@ -0,0 +1 @@ +be3563ae3f795b2b4353bcce3a527ad0a4f7f644 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_small_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_small_wd/dot_git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/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/README.txt b/LibGit2Sharp.Tests/Resources/submodule_target_wd/README.txt new file mode 100644 index 000000000..780d7397f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/README.txt @@ -0,0 +1,3 @@ +This is the target for submod2 submodule links. +Don't add commits casually because you make break tests. + diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/config new file mode 100644 index 000000000..af107929f --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/index new file mode 100644 index 000000000..eb3ff8c10 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/info/exclude new file mode 100644 index 000000000..a5196d1be --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 new file mode 100644 index 000000000..f4b7094c5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 new file mode 100644 index 000000000..56c845e49 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 new file mode 100644 index 000000000..bd179b5f5 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/41/bd4bc3df978de695f67ace64c560913da11653 new file mode 100644 index 000000000..ccf49bd15 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 new file mode 100644 index 000000000..53029069a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/5e/4963595a9774b90524d35a807169049de8ccad new file mode 100644 index 000000000..38c791eba Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/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_target_wd/dot_git/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/73/ba924a80437097795ae839e66e187c55d3babf new file mode 100644 index 000000000..83d1ba481 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/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_target_wd/dot_git/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/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_target_wd/dot_git/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e new file mode 100644 index 000000000..83cc29fb1 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 new file mode 100644 index 000000000..55bda40ef Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/dot_git/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_target_wd/file_to_modify b/LibGit2Sharp.Tests/Resources/submodule_target_wd/file_to_modify new file mode 100644 index 000000000..789efbdad --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_target_wd/file_to_modify @@ -0,0 +1,3 @@ +This is a file to modify in submodules +It already has some history. +You can add local changes as needed. diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/README.txt b/LibGit2Sharp.Tests/Resources/submodule_wd/README.txt new file mode 100644 index 000000000..f990a25a7 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/README.txt @@ -0,0 +1,3 @@ +This is the submodule test data +This repo will have a bunch of submodules in different states + diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config new file mode 100644 index 000000000..7eada4ff5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/config @@ -0,0 +1,22 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true +[submodule "sm_missing_commits"] + url = ../submodule_target_wd +[submodule "sm_unchanged"] + url = ../submodule_target_wd +[submodule "sm_changed_file"] + url = ../submodule_target_wd +[submodule "sm_changed_index"] + url = ../submodule_target_wd +[submodule "sm_changed_head"] + url = ../submodule_target_wd +[submodule "sm_changed_untracked_file"] + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/index new file mode 100644 index 000000000..0c17e8629 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..e37936d26 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_added_and_uncommited + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/index new file mode 100644 index 000000000..65140a510 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/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_added_and_uncommited/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_added_and_uncommited/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master 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/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/HEAD @@ -0,0 +1 @@ +ref: refs/heads/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 new file mode 100644 index 000000000..9812c64b3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_file + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/index new file mode 100644 index 000000000..6914a3b6e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_changed_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_file/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_head/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..6d46e37ac --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_head + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/index new file mode 100644 index 000000000..728fa292f Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 new file mode 100644 index 000000000..a2c371642 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/3d/9386c507f6b093471a3e324085657a3c2b4247 @@ -0,0 +1,3 @@ +xKj!E3vj#<<@Ouq .)ql osFa#v )g#{':Tl`b40 ;fr4 + +zU-/glm\'LjrhXG_+l ʚE`;=]J \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 new file mode 100644 index 000000000..f8a236f3d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/77/fb0ed3e58568d6ad362c78de08ab8649d76e29 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 new file mode 100644 index 000000000..8155b3e87 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/8e/b1e637ed9fc8e5454fa20d38f809091f9395f4 @@ -0,0 +1,2 @@ +xMM; +1) ZPr3k lEnl!crz ,e +lEZxuPYx QC*fuLcfR3T0'үj~G^s1b2zVk]5<v'> \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_changed_head/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/refs/heads/master new file mode 100644 index 000000000..ae079bd79 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/refs/heads/master @@ -0,0 +1 @@ +3d9386c507f6b093471a3e324085657a3c2b4247 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_head/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_index/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..94490e330 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_index + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/index new file mode 100644 index 000000000..6fad3b43e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 new file mode 100644 index 000000000..cb3f5a002 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/a0/2d31770687965547ab7a04cee199b29ee458d6 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_changed_index/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_index/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_untracked_file/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..e5ff0780a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_changed_untracked_file + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/index new file mode 100644 index 000000000..598e30a32 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/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_changed_untracked_file/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_changed_untracked_file/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..376a475b5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_missing_commits + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/index new file mode 100644 index 000000000..490356524 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/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_missing_commits/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/packed-refs new file mode 100644 index 000000000..66fbf5daf --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +5e4963595a9774b90524d35a807169049de8ccad refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/heads/master new file mode 100644 index 000000000..3913aca5d --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/heads/master @@ -0,0 +1 @@ +5e4963595a9774b90524d35a807169049de8ccad diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_missing_commits/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master 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 new file mode 100644 index 000000000..ea79f0e26 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/config @@ -0,0 +1,13 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + worktree = ../../../sm_unchanged + ignorecase = true +[remote "origin"] + fetch = +refs/heads/*:refs/remotes/origin/* + 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/index b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/index new file mode 100644 index 000000000..629c849ec Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/index differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/06/362fe2fdb7010d0e447b4fb450d405420479a1 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/0e/6a3ca48bd47cfe67681acf39aa0b10a0b92484 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/17/d0ece6e96460a06592d9d9d000de37ba4232c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/41/bd4bc3df978de695f67ace64c560913da11653 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/48/0095882d281ed676fe5b863569520e54a7d5c0 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/5e/4963595a9774b90524d35a807169049de8ccad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/6b/31c659545507c381e9cd34ec508f16c04e149e new file mode 100644 index 000000000..a26d29993 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/73/ba924a80437097795ae839e66e187c55d3babf differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/78/0d7397f5e8f8f477fb55b7af3accc2154b2d4a new file mode 100644 index 000000000..6d27af8a8 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/78/9efbdadaa4a582778d4584385495559ea0994b new file mode 100644 index 000000000..17458840b --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/88/34b635dd468a83cb012f6feace968c1c9f5d6e differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/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_unchanged/objects/d0/5f2cd5cc77addf68ed6f50d622c9a4f732e6c5 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/packed-refs b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/packed-refs new file mode 100644 index 000000000..5a4ebc47c --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/packed-refs @@ -0,0 +1,2 @@ +# pack-refs with: peeled +480095882d281ed676fe5b863569520e54a7d5c0 refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/heads/master b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/heads/master new file mode 100644 index 000000000..e12c44d7a --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/heads/master @@ -0,0 +1 @@ +480095882d281ed676fe5b863569520e54a7d5c0 diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/remotes/origin/HEAD b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/remotes/origin/HEAD new file mode 100644 index 000000000..6efe28fff --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/modules/sm_unchanged/refs/remotes/origin/HEAD @@ -0,0 +1 @@ +ref: refs/remotes/origin/master diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e new file mode 100644 index 000000000..f1ea5f4c8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/09/460e5b6cbcb05a3e404593c32a3aa7221eca0e differ 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/14/fe9ccf104058df25e0a08361c4494e167ef243 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 new file mode 100644 index 000000000..d3c8582e3 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/14/fe9ccf104058df25e0a08361c4494e167ef243 @@ -0,0 +1 @@ +xM F]sfhcc;ހ@I(_O{s%@> ^!F'!諲l_q4E޶RݏS'n>>m^\w^$X_迦xE_.9} \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 new file mode 100644 index 000000000..fce6a94b5 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/22/ce3e0311dda73a5992d54a4a595518d3876ea7 @@ -0,0 +1,4 @@ +x +0Eݶ_Q. +. W"!1 !3 >+.9=3W(n-:;j[" W{ޕQZW,2 iviyh T/={ !@b(bJcSPrŌ +{`|%imp콡=IÿW26B@)|)g \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 new file mode 100644 index 000000000..2965becf6 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/25/5546424b0efb847b1bfc91dbf7348b277f8970 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad new file mode 100644 index 000000000..08faf0fa8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/2a/30f1e6f94b20917005a21273f65b406d0f8bad differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 new file mode 100644 index 000000000..ee7848ae6 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/42/cfb95cd01bf9225b659b5ee3edcc78e8eeb478 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 new file mode 100644 index 000000000..ca9203a6e Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/57/958699c2dc394f81cfc76950e9c3ac3025c398 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/59/01da4f1c67756eeadc5121d206bec2431f253b b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/59/01da4f1c67756eeadc5121d206bec2431f253b new file mode 100644 index 000000000..9f88f6bdf --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/59/01da4f1c67756eeadc5121d206bec2431f253b @@ -0,0 +1,2 @@ +x 1ENӀ2yg@D,A$;YE6m{΁e(/2d#jȚL 2Yd:fʞ =V^DhR $^a+tn {n8xs +Ծ=<@jCŹک[>6#=-g?,F \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d new file mode 100644 index 000000000..30bee40e9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/60/7d96653d4d0a4f733107f7890c2e67b55b620d 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/74/84482eb8db738cafa696993664607500a3f2b9 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/74/84482eb8db738cafa696993664607500a3f2b9 new file mode 100644 index 000000000..79018042d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/74/84482eb8db738cafa696993664607500a3f2b9 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 new file mode 100644 index 000000000..cde89e5bb Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/7b/a4c5c3561daa5ab1a86215cfb0587e96d404d6 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 new file mode 100644 index 000000000..41af98aa9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/87/3585b94bdeabccea991ea5e3ec1a277895b698 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/97/4cf7c73de336b0c4e019f918f3cee367d72e84 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 new file mode 100644 index 000000000..160f1caf4 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/97/4cf7c73de336b0c4e019f918f3cee367d72e84 @@ -0,0 +1,2 @@ +x +0Eݶ_Bqg yi IYUp!΁s|R7=)XCAG:25:<-uU_IY\Ϥ%AF f{G qTPsu(Z{RA #̉0mŲ.8b?{vʌ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 new file mode 100644 index 000000000..1ee52218d Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/9d/bc299bc013ea253583b40bf327b5a6e4037b89 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/a9/104bf89e911387244ef499413960ba472066d9 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a9/104bf89e911387244ef499413960ba472066d9 new file mode 100644 index 000000000..2239e14a8 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/a9/104bf89e911387244ef499413960ba472066d9 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb new file mode 100644 index 000000000..a03ea66e4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/b6/14088620bbdc1d29549d223ceba0f4419fd4cb 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/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 new file mode 100644 index 000000000..292303eb9 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/d4/07f19e50c1da1ff584beafe0d6dac7237c5d06 differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 new file mode 100644 index 000000000..b92c7eebd --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/d9/3e95571d92cceb5de28c205f1d5f3cc8b88bc8 @@ -0,0 +1,2 @@ +x +!nק} ".zuRCx}Αہt .׫6,is&%9S#IW=aߐf2ABYsНa{c^K3gwM͠Fߥ4s'NI \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c new file mode 100644 index 000000000..3c7750b12 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/e3/b83bf274ee065eee48734cf8c6dfaf5e81471c differ diff --git a/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 new file mode 100644 index 000000000..219620b25 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/submodule_wd/dot_git/objects/f5/4414c25e6d24fe39f5c3f128d7c8a17bc23833 @@ -0,0 +1,2 @@ +xe +0aSbOz12\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" - -# This example catches duplicate Signed-off-by lines. - -test "" = "$(grep '^Signed-off-by: ' "$1" | - sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { - echo >&2 Duplicate Signed-off-by lines. - exit 1 -} diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-commit.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-commit.sample deleted file mode 100644 index 9d3b4be7d..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-commit.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script that is called after a successful -# commit is made. -# -# To enable this hook, rename this file to "post-commit". - -: Nothing diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-receive.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-receive.sample deleted file mode 100644 index 95e87f966..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-receive.sample +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# An example hook script for the "post-receive" event. -# -# The "post-receive" script is run after receive-pack has accepted a pack -# and the repository has been updated. It is passed arguments in through -# stdin in the form -# -# For example: -# aa453216d1b3e49e7f6f98441fa56946ddcd6a20 68f7abf4e6f922807889f52bc043ecd31b79f814 refs/heads/master -# -# see contrib/hooks/ for a sample, or uncomment the next line and -# rename the file to "post-receive". - -#. /usr/share/doc/git-core/contrib/hooks/post-receive-email diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-update.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-update.sample deleted file mode 100644 index 93e30366f..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/post-update.sample +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare a packed repository for use over -# dumb transports. -# -# To enable this hook, rename this file to "post-update". - -exec git update-server-info diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-applypatch.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-applypatch.sample deleted file mode 100644 index e015e761d..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-applypatch.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed -# by applypatch from an e-mail message. -# -# The hook should exit with non-zero status after issuing an -# appropriate message if it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-applypatch". - -. git-sh-setup -test -x "$GIT_DIR/hooks/pre-commit" && - exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"} -: diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-commit.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-commit.sample deleted file mode 100644 index f84e5144a..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-commit.sample +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh -# -# An example hook script to verify what is about to be committed. -# Called by "git commit" with no arguments. The hook should -# exit with non-zero status after issuing an appropriate message if -# it wants to stop the commit. -# -# To enable this hook, rename this file to "pre-commit". - -if git rev-parse --verify HEAD >/dev/null 2>&1 -then - against=HEAD -else - # Initial commit: diff against an empty tree object - against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 -fi - -# If you want to allow non-ascii filenames set this variable to true. -allownonascii=$(git config hooks.allownonascii) - -# Cross platform projects tend to avoid non-ascii filenames; prevent -# them from being added to the repository. We exploit the fact that the -# printable range starts at the space character and ends with tilde. -if [ "$allownonascii" != "true" ] && - # Note that the use of brackets around a tr range is ok here, (it's - # even required, for portability to Solaris 10's /usr/bin/tr), since - # the square bracket bytes happen to fall in the designated range. - test "$(git diff --cached --name-only --diff-filter=A -z $against | - LC_ALL=C tr -d '[ -~]\0')" -then - echo "Error: Attempt to add a non-ascii file name." - echo - echo "This can cause problems if you want to work" - echo "with people on other platforms." - echo - echo "To be portable it is advisable to rename the file ..." - echo - echo "If you know what you are doing you can disable this" - echo "check using:" - echo - echo " git config hooks.allownonascii true" - echo - exit 1 -fi - -exec git diff-index --check --cached $against -- diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-rebase.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-rebase.sample deleted file mode 100644 index d9c5522e6..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/pre-rebase.sample +++ /dev/null @@ -1,169 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2006, 2008 Junio C Hamano -# -# The "pre-rebase" hook is run just before "git rebase" starts doing -# its job, and can prevent the command from running by exiting with -# non-zero status. -# -# The hook is called with the following parameters: -# -# $1 -- the upstream the series was forked from. -# $2 -- the branch being rebased (or empty when rebasing the current branch). -# -# This sample shows how to prevent topic branches that are already -# merged to 'next' branch from getting rebased, because allowing it -# would result in rebasing already published history. - -publish=next -basebranch="$1" -if test "$#" = 2 -then - topic="refs/heads/$2" -else - topic=`git symbolic-ref HEAD` || - exit 0 ;# we do not interrupt rebasing detached HEAD -fi - -case "$topic" in -refs/heads/??/*) - ;; -*) - exit 0 ;# we do not interrupt others. - ;; -esac - -# Now we are dealing with a topic branch being rebased -# on top of master. Is it OK to rebase it? - -# Does the topic really exist? -git show-ref -q "$topic" || { - echo >&2 "No such branch $topic" - exit 1 -} - -# Is topic fully merged to master? -not_in_master=`git rev-list --pretty=oneline ^master "$topic"` -if test -z "$not_in_master" -then - echo >&2 "$topic is fully merged to master; better remove it." - exit 1 ;# we could allow it, but there is no point. -fi - -# Is topic ever merged to next? If so you should not be rebasing it. -only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` -only_next_2=`git rev-list ^master ${publish} | sort` -if test "$only_next_1" = "$only_next_2" -then - not_in_topic=`git rev-list "^$topic" master` - if test -z "$not_in_topic" - then - echo >&2 "$topic is already up-to-date with master" - exit 1 ;# we could allow it, but there is no point. - else - exit 0 - fi -else - not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` - /usr/bin/perl -e ' - my $topic = $ARGV[0]; - my $msg = "* $topic has commits already merged to public branch:\n"; - my (%not_in_next) = map { - /^([0-9a-f]+) /; - ($1 => 1); - } split(/\n/, $ARGV[1]); - for my $elem (map { - /^([0-9a-f]+) (.*)$/; - [$1 => $2]; - } split(/\n/, $ARGV[2])) { - if (!exists $not_in_next{$elem->[0]}) { - if ($msg) { - print STDERR $msg; - undef $msg; - } - print STDERR " $elem->[1]\n"; - } - } - ' "$topic" "$not_in_next" "$not_in_master" - exit 1 -fi - -exit 0 - -################################################################ - -This sample hook safeguards topic branches that have been -published from being rewound. - -The workflow assumed here is: - - * Once a topic branch forks from "master", "master" is never - merged into it again (either directly or indirectly). - - * Once a topic branch is fully cooked and merged into "master", - it is deleted. If you need to build on top of it to correct - earlier mistakes, a new topic branch is created by forking at - the tip of the "master". This is not strictly necessary, but - it makes it easier to keep your history simple. - - * Whenever you need to test or publish your changes to topic - branches, merge them into "next" branch. - -The script, being an example, hardcodes the publish branch name -to be "next", but it is trivial to make it configurable via -$GIT_DIR/config mechanism. - -With this workflow, you would want to know: - -(1) ... if a topic branch has ever been merged to "next". Young - topic branches can have stupid mistakes you would rather - clean up before publishing, and things that have not been - merged into other branches can be easily rebased without - affecting other people. But once it is published, you would - not want to rewind it. - -(2) ... if a topic branch has been fully merged to "master". - Then you can delete it. More importantly, you should not - build on top of it -- other people may already want to - change things related to the topic as patches against your - "master", so if you need further changes, it is better to - fork the topic (perhaps with the same name) afresh from the - tip of "master". - -Let's look at this example: - - o---o---o---o---o---o---o---o---o---o "next" - / / / / - / a---a---b A / / - / / / / - / / c---c---c---c B / - / / / \ / - / / / b---b C \ / - / / / / \ / - ---o---o---o---o---o---o---o---o---o---o---o "master" - - -A, B and C are topic branches. - - * A has one fix since it was merged up to "next". - - * B has finished. It has been fully merged up to "master" and "next", - and is ready to be deleted. - - * C has not merged to "next" at all. - -We would want to allow C to be rebased, refuse A, and encourage -B to be deleted. - -To compute (1): - - git rev-list ^master ^topic next - git rev-list ^master next - - if these match, topic has not merged in next at all. - -To compute (2): - - git rev-list master..topic - - if this is empty, it is fully merged to "master". diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/prepare-commit-msg.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/prepare-commit-msg.sample deleted file mode 100644 index d48c8b712..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/prepare-commit-msg.sample +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# -# An example hook script to prepare the commit log message. -# Called by "git commit" with the name of the file that has the -# commit message, followed by the description of the commit -# message's source. The hook's purpose is to edit the commit -# message file. If the hook fails with a non-zero status, -# the commit is aborted. -# -# To enable this hook, rename this file to "prepare-commit-msg". - -# This hook includes three examples. The first comments out the -# "Conflicts:" part of a merge commit. -# -# The second includes the output of "git diff --name-status -r" -# into the message, just before the "git status" output. It is -# commented because it doesn't cope with --amend or with squashed -# commits. -# -# The third example adds a Signed-off-by line to the message, that can -# still be edited. This is rarely a good idea. - -case "$2,$3" in - merge,) - /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; - -# ,|template,) -# /usr/bin/perl -i.bak -pe ' -# print "\n" . `git diff --cached --name-status -r` -# if /^#/ && $first++ == 0' "$1" ;; - - *) ;; -esac - -# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') -# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/update.sample b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/update.sample deleted file mode 100644 index 032fe11cd..000000000 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/hooks/update.sample +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -# -# An example hook script to blocks unannotated tags from entering. -# Called by "git receive-pack" with arguments: refname sha1-old sha1-new -# -# To enable this hook, rename this file to "update". -# -# Config -# ------ -# hooks.allowunannotated -# This boolean sets whether unannotated tags will be allowed into the -# repository. By default they won't be. -# hooks.allowdeletetag -# This boolean sets whether deleting tags will be allowed in the -# repository. By default they won't be. -# hooks.allowmodifytag -# This boolean sets whether a tag may be modified after creation. By default -# it won't be. -# hooks.allowdeletebranch -# This boolean sets whether deleting branches will be allowed in the -# repository. By default they won't be. -# hooks.denycreatebranch -# This boolean sets whether remotely creating branches will be denied -# in the repository. By default this is allowed. -# - -# --- Command line -refname="$1" -oldrev="$2" -newrev="$3" - -# --- Safety check -if [ -z "$GIT_DIR" ]; then - echo "Don't run this script from the command line." >&2 - echo " (if you want, you could supply GIT_DIR then run" >&2 - echo " $0 )" >&2 - exit 1 -fi - -if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then - echo "Usage: $0 " >&2 - exit 1 -fi - -# --- Config -allowunannotated=$(git config --bool hooks.allowunannotated) -allowdeletebranch=$(git config --bool hooks.allowdeletebranch) -denycreatebranch=$(git config --bool hooks.denycreatebranch) -allowdeletetag=$(git config --bool hooks.allowdeletetag) -allowmodifytag=$(git config --bool hooks.allowmodifytag) - -# check for no description -projectdesc=$(sed -e '1q' "$GIT_DIR/description") -case "$projectdesc" in -"Unnamed repository"* | "") - echo "*** Project description file hasn't been set" >&2 - exit 1 - ;; -esac - -# --- Check types -# if $newrev is 0000...0000, it's a commit to delete a ref. -zero="0000000000000000000000000000000000000000" -if [ "$newrev" = "$zero" ]; then - newrev_type=delete -else - newrev_type=$(git cat-file -t $newrev) -fi - -case "$refname","$newrev_type" in - refs/tags/*,commit) - # un-annotated tag - short_refname=${refname##refs/tags/} - if [ "$allowunannotated" != "true" ]; then - echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 - exit 1 - fi - ;; - refs/tags/*,delete) - # delete tag - if [ "$allowdeletetag" != "true" ]; then - echo "*** Deleting a tag is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/tags/*,tag) - # annotated tag - if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 - then - echo "*** Tag '$refname' already exists." >&2 - echo "*** Modifying a tag is not allowed in this repository." >&2 - exit 1 - fi - ;; - refs/heads/*,commit) - # branch - if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then - echo "*** Creating a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/heads/*,delete) - # delete branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - refs/remotes/*,commit) - # tracking branch - ;; - refs/remotes/*,delete) - # delete tracking branch - if [ "$allowdeletebranch" != "true" ]; then - echo "*** Deleting a tracking branch is not allowed in this repository" >&2 - exit 1 - fi - ;; - *) - # Anything else (is there anything else?) - echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 - exit 1 - ;; -esac - -# --- Finished -exit 0 diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/index b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/index index ba522b9fd..43efbb719 100644 Binary files a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/index and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/index differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/f7/05abffe7015f2beacf2abe7a36583ebee3487e new file mode 100644 index 000000000..7490425a2 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/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/testrepo_wd/dot_git/refs/heads/treesame_as_32eab b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/refs/heads/treesame_as_32eab new file mode 100644 index 000000000..2f412c398 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/refs/heads/treesame_as_32eab @@ -0,0 +1 @@ +f705abffe7015f2beacf2abe7a36583ebee3487e 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/worktree/testrepo_wd/dot_git/info/exclude b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude new file mode 100644 index 000000000..f00680973 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/worktree/testrepo_wd/dot_git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/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 new file mode 100644 index 000000000..c43479f0f --- /dev/null +++ b/LibGit2Sharp.Tests/RevertFixture.cs @@ -0,0 +1,527 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class RevertFixture : BaseFixture + { + [Fact] + public void CanRevert() + { + // The branch name to perform the revert on, + // and the file whose contents we expect to be reverted. + const string revertBranchName = "refs/heads/revert"; + const string revertedFile = "a.txt"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + // Revert tip commit. + RevertResult result = repo.Revert(repo.Head.Tip, Constants.Signature); + Assert.NotNull(result); + Assert.Equal(RevertStatus.Reverted, result.Status); + + // Verify commit was made. + Assert.NotNull(result.Commit); + + // Verify the expected commit ID. + Assert.Equal("04746060fa753c9970d88a0b59151d7b212ac903", result.Commit.Id.Sha); + + // Verify workspace is clean. + Assert.True(repo.Index.IsFullyMerged); + Assert.False(repo.RetrieveStatus().IsDirty); + + // Lookup the blob containing the expected reverted content of a.txt. + Blob expectedBlob = repo.Lookup("bc90ea420cf6c5ae3db7dcdffa0d79df567f219b"); + Assert.NotNull(expectedBlob); + + // Verify contents of Index. + IndexEntry revertedIndexEntry = repo.Index[revertedFile]; + Assert.NotNull(revertedIndexEntry); + + // Verify the contents of the index. + Assert.Equal(expectedBlob.Id, revertedIndexEntry.Id); + + // Verify contents of workspace. + string fullPath = Path.Combine(repo.Info.WorkingDirectory, revertedFile); + Assert.Equal(expectedBlob.GetContentText(new FilteringOptions(revertedFile)), File.ReadAllText(fullPath)); + } + } + + [Fact] + public void CanRevertAndNotCommit() + { + // The branch name to perform the revert on, + // and the file whose contents we expect to be reverted. + const string revertBranchName = "refs/heads/revert"; + const string revertedFile = "a.txt"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + // Revert tip commit. + RevertResult result = repo.Revert(repo.Head.Tip, Constants.Signature, new RevertOptions() { CommitOnSuccess = false }); + Assert.NotNull(result); + Assert.Equal(RevertStatus.Reverted, result.Status); + + // Verify the commit was made. + Assert.Null(result.Commit); + + // Verify workspace is dirty. + 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"); + Assert.NotNull(expectedBlob); + + // Verify contents of Index. + IndexEntry revertedIndexEntry = repo.Index[revertedFile]; + Assert.NotNull(revertedIndexEntry); + + Assert.Equal(expectedBlob.Id, revertedIndexEntry.Id); + + // Verify contents of workspace. + string fullPath = Path.Combine(repo.Info.WorkingDirectory, revertedFile); + Assert.Equal(expectedBlob.GetContentText(new FilteringOptions(revertedFile)), File.ReadAllText(fullPath)); + } + } + + [Fact] + public void RevertWithConflictDoesNotCommit() + { + // 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)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + // 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. + RevertResult result = repo.Revert(commitToRevert, Constants.Signature); + Assert.NotNull(result); + Assert.Equal(RevertStatus.Conflicts, result.Status); + Assert.Null(result.Commit); + + // Verify there is a conflict on the expected path. + Assert.False(repo.Index.IsFullyMerged); + Assert.NotNull(repo.Index.Conflicts["a.txt"]); + + // Verify the non-conflicting paths are staged. + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("b.txt")); + Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus("c.txt")); + } + } + + [Theory] + [InlineData(CheckoutFileConflictStrategy.Ours)] + [InlineData(CheckoutFileConflictStrategy.Theirs)] + public void RevertWithFileConflictStrategyOption(CheckoutFileConflictStrategy conflictStrategy) + { + // The branch name to perform the revert on, + // and the file which we expect conflicts as result of the revert. + const string revertBranchName = "refs/heads/revert"; + const string conflictedFilePath = "a.txt"; + + string path = SandboxRevertTestRepo(); + using (var repo = new Repository(path)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + // Specify FileConflictStrategy. + RevertOptions options = new RevertOptions() + { + FileConflictStrategy = conflictStrategy, + }; + + 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); + + Conflict conflict = repo.Index.Conflicts[conflictedFilePath]; + Assert.NotNull(conflict); + + Assert.NotNull(conflict); + Assert.NotNull(conflict.Theirs); + Assert.NotNull(conflict.Ours); + + // Get the blob containing the expected content. + Blob expectedBlob = null; + switch (conflictStrategy) + { + case CheckoutFileConflictStrategy.Theirs: + expectedBlob = repo.Lookup(conflict.Theirs.Id); + break; + case CheckoutFileConflictStrategy.Ours: + expectedBlob = repo.Lookup(conflict.Ours.Id); + break; + default: + throw new Exception("Unexpected FileConflictStrategy"); + } + + Assert.NotNull(expectedBlob); + + // Check the content of the file on disk matches what is expected. + string expectedContent = expectedBlob.GetContentText(new FilteringOptions(conflictedFilePath)); + Assert.Equal(expectedContent, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, conflictedFilePath))); + } + } + + [Fact] + public void RevertReportsCheckoutProgress() + { + const string revertBranchName = "refs/heads/revert"; + + string repoPath = SandboxRevertTestRepo(); + using (var repo = new Repository(repoPath)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + bool wasCalled = false; + + RevertOptions options = new RevertOptions() + { + OnCheckoutProgress = (path, completed, total) => wasCalled = true + }; + + repo.Revert(repo.Head.Tip, Constants.Signature, options); + + Assert.True(wasCalled); + } + } + + [Fact] + public void RevertReportsCheckoutNotification() + { + const string revertBranchName = "refs/heads/revert"; + + string repoPath = SandboxRevertTestRepo(); + using (var repo = new Repository(repoPath)) + { + // Checkout the revert branch. + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + bool wasCalled = false; + CheckoutNotifyFlags actualNotifyFlags = CheckoutNotifyFlags.None; + + RevertOptions options = new RevertOptions() + { + OnCheckoutNotify = (path, notificationType) => { wasCalled = true; actualNotifyFlags = notificationType; return true; }, + CheckoutNotifyFlags = CheckoutNotifyFlags.Updated, + }; + + repo.Revert(repo.Head.Tip, Constants.Signature, options); + + Assert.True(wasCalled); + Assert.Equal(CheckoutNotifyFlags.Updated, actualNotifyFlags); + } + } + + [Theory] + [InlineData(null)] + [InlineData(true)] + [InlineData(false)] + public void RevertFindsRenames(bool? findRenames) + { + // The environment is set up such that: + // - file d.txt is edited in the commit that is to be reverted (commit A) + // - file d.txt is renamed to d_renamed.txt + // - commit A is reverted. + // If rename detection is enabled, then the revert is applied + // to d_renamed.txt. If rename detection is not enabled, + // then the revert results in a conflict. + const string revertBranchName = "refs/heads/revert_rename"; + const string commitIdToRevert = "ca3e813"; + const string expectedBlobId = "0ff3bbb9c8bba2291654cd64067fa417ff54c508"; + const string modifiedFilePath = "d_renamed.txt"; + + string repoPath = SandboxRevertTestRepo(); + using (var repo = new Repository(repoPath)) + { + Branch currentBranch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(currentBranch); + + Commit commitToRevert = repo.Lookup(commitIdToRevert); + Assert.NotNull(currentBranch); + + RevertOptions options; + if (findRenames.HasValue) + { + options = new RevertOptions() + { + FindRenames = findRenames.Value, + }; + } + else + { + options = new RevertOptions(); + } + + RevertResult result = repo.Revert(commitToRevert, Constants.Signature, options); + Assert.NotNull(result); + + if (!findRenames.HasValue || + findRenames.Value == true) + { + Assert.Equal(RevertStatus.Reverted, result.Status); + Assert.NotNull(result.Commit); + Blob expectedBlob = repo.Lookup(expectedBlobId); + Assert.NotNull(expectedBlob); + + GitObject blob = result.Commit.Tree[modifiedFilePath].Target as Blob; + Assert.NotNull(blob); + Assert.Equal(blob.Id, expectedBlob.Id); + + // Verify contents of workspace + string fullPath = Path.Combine(repo.Info.WorkingDirectory, modifiedFilePath); + Assert.Equal(expectedBlob.GetContentText(new FilteringOptions(modifiedFilePath)), File.ReadAllText(fullPath)); + } + else + { + Assert.Equal(RevertStatus.Conflicts, result.Status); + Assert.Null(result.Commit); + } + } + } + + [Theory] + [InlineData(1, "a04ef5f22c2413a9743046436c0e5354ed903f78")] + [InlineData(2, "1ae0cd88802bb4f4e6413ba63e41376d235b6fd0")] + public void CanRevertMergeCommit(int mainline, string expectedId) + { + const string revertBranchName = "refs/heads/revert_merge"; + const string commitIdToRevert = "2747045"; + + string repoPath = SandboxRevertTestRepo(); + using (var repo = new Repository(repoPath)) + { + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + Commit commitToRevert = repo.Lookup(commitIdToRevert); + Assert.NotNull(commitToRevert); + + RevertOptions options = new RevertOptions() + { + Mainline = mainline, + }; + + RevertResult result = repo.Revert(commitToRevert, Constants.Signature, options); + + Assert.NotNull(result); + Assert.Equal(RevertStatus.Reverted, result.Status); + Assert.Equal(result.Commit.Sha, expectedId); + + 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.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) + { + // 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.RetrieveStatus("d_renamed.txt")); + + // This is the commit containing the expected contents of "d_renamed.txt". + Commit commit = repo.Lookup("c4b5cea70e4cd5b633ed0f10ae0ed5384e8190d8"); + Assert.NotNull(commit); + Assert.Equal(commit["d_renamed.txt"].Target.Id, repo.Index["d_renamed.txt"].Id); + + // This is the commit containing the expected contents of a.txt. + commit = repo.Lookup("cb4f7f0eca7a0114cdafd8537332aa17de36a4e9"); + Assert.NotNull(commit); + Assert.Equal(commit["a.txt"].Target.Id, repo.Index["a.txt"].Id); + } + } + } + + [Fact] + public void CanNotRevertAMergeCommitWithoutSpecifyingTheMainlineBranch() + { + const string revertBranchName = "refs/heads/revert_merge"; + const string commitIdToRevert = "2747045"; + + string repoPath = SandboxRevertTestRepo(); + using (var repo = new Repository(repoPath)) + { + Branch branch = Commands.Checkout(repo, revertBranchName); + Assert.NotNull(branch); + + var commitToRevert = repo.Lookup(commitIdToRevert); + Assert.NotNull(commitToRevert); + + Assert.Throws(() => repo.Revert(commitToRevert, Constants.Signature)); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void RevertWithNothingToRevert(bool commitOnSuccess) + { + // 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); + + // Revert the same commit a second time + result = repo.Revert( + commitToRevert, + Constants.Signature, + new RevertOptions() { CommitOnSuccess = commitOnSuccess }); + + Assert.NotNull(result); + Assert.Null(result.Commit); + Assert.Equal(RevertStatus.NothingToRevert, result.Status); + + if (commitOnSuccess) + { + Assert.Equal(CurrentOperation.None, repo.Info.CurrentOperation); + } + else + { + Assert.Equal(CurrentOperation.Revert, repo.Info.CurrentOperation); + } + } + } + + [Fact] + public void RevertOrphanedBranchThrows() + { + // 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; + + // Move the HEAD to an orphaned branch. + repo.Refs.UpdateTarget("HEAD", "refs/heads/orphan"); + Assert.True(repo.Info.IsHeadUnborn); + + // Revert the tip of the refs/heads/revert branch. + 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 new file mode 100644 index 000000000..e40cabd6c --- /dev/null +++ b/LibGit2Sharp.Tests/SignatureFixture.cs @@ -0,0 +1,41 @@ +using System; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class SignatureFixture : BaseFixture + { + [Theory] + [InlineData("\0Leading zero")] + [InlineData("Trailing zero\0")] + [InlineData("Zero \0inside")] + [InlineData("\0")] + [InlineData("\0\0\0")] + public void CreatingASignatureWithANameContainingZerosThrows(string name) + { + Assert.Throws(() => new Signature(name, "me@there.com", DateTimeOffset.Now)); + } + + [Theory] + [InlineData("\0Leading@zero.com")] + [InlineData("Trailing@zero.com\0")] + [InlineData("Zero@\0inside.com")] + [InlineData("\0")] + [InlineData("\0\0\0")] + public void CreatingASignatureWithAnEmailContainingZerosThrows(string email) + { + Assert.Throws(() => new Signature("Me", email, DateTimeOffset.Now)); + } + + [Fact] + 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)); + Assert.Throws(() => new Signature("Me", string.Empty, DateTimeOffset.Now)); + } + } +} diff --git a/LibGit2Sharp.Tests/StageFixture.cs b/LibGit2Sharp.Tests/StageFixture.cs new file mode 100644 index 000000000..c087aa7be --- /dev/null +++ b/LibGit2Sharp.Tests/StageFixture.cs @@ -0,0 +1,413 @@ +using System; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + 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.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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Commands.Stage(repo, relativePath); + + Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); + Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); + 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 = 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.NewInIndex, repo.RetrieveStatus(filename)); + + Touch(repo.Info.WorkingDirectory, filename, "brand new content"); + Assert.Equal(FileStatus.NewInIndex | FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(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.NewInIndex, repo.RetrieveStatus(filename)); + } + } + + [Theory] + [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] + public void StagingAnUnknownFileThrowsIfExplicitPath(string relativePath, FileStatus status) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Index[relativePath]); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + + 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.DeletedFromIndex)] + public void CanStageAnUnknownFileWithLaxUnmatchedExplicitPathsValidation(string relativePath, FileStatus status) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Index[relativePath]); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); + + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + } + } + + [Theory] + [InlineData("1/I-do-not-exist.txt", FileStatus.Nonexistent)] + [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex)] + public void StagingAnUnknownFileWithLaxExplicitPathsValidationDoesntThrow(string relativePath, FileStatus status) + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + Assert.Null(repo.Index[relativePath]); + Assert.Equal(status, repo.RetrieveStatus(relativePath)); + + Commands.Stage(repo, relativePath); + Commands.Stage(repo, relativePath, new StageOptions { ExplicitPathsOptions = new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false } }); + } + } + + [Fact] + public void CanStageTheRemovalOfAStagedFile() + { + 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.NewInIndex, repo.RetrieveStatus(filename)); + + File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); + Assert.Equal(FileStatus.NewInIndex | FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); + + Commands.Stage(repo, filename); + Assert.Null(repo.Index[filename]); + + Assert.Equal(count - 1, repo.Index.Count); + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(filename)); + } + } + + [Theory] + [InlineData("unit_test.txt")] + [InlineData("!unit_test.txt")] + [InlineData("!bang/unit_test.txt")] + public void CanStageANewFileInAPersistentManner(string filename) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(filename)); + Assert.Null(repo.Index[filename]); + + Touch(repo.Info.WorkingDirectory, filename, "some contents"); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename)); + Assert.Null(repo.Index[filename]); + + Commands.Stage(repo, filename); + Assert.NotNull(repo.Index[filename]); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + } + + using (var repo = new Repository(path)) + { + Assert.NotNull(repo.Index[filename]); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + } + } + + [SkippableTheory] + [InlineData(false)] + [InlineData(true)] + public void CanStageANewFileWithAFullPath(bool ignorecase) + { + // Skipping due to ignorecase issue in libgit2. + // See: https://github.com/libgit2/libgit2/pull/1689. + InconclusiveIf(() => ignorecase, + "Skipping 'ignorecase = true' test due to ignorecase issue in libgit2."); + + //InconclusiveIf(() => IsFileSystemCaseSensitive && ignorecase, + // "Skipping 'ignorecase = true' test on case-sensitive file system."); + + string path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) + { + repo.Config.Set("core.ignorecase", ignorecase); + } + + using (var repo = new Repository(path)) + { + const string filename = "new_untracked_file.txt"; + string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); + Assert.True(File.Exists(fullPath)); + + AssertStage(null, repo, fullPath); + AssertStage(ignorecase, repo, fullPath.ToUpperInvariant()); + AssertStage(ignorecase, repo, fullPath.ToLowerInvariant()); + } + } + + private static void AssertStage(bool? ignorecase, IRepository repo, string path) + { + try + { + 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) + { + Assert.False(ignorecase ?? true); + } + } + + [Fact] + public void CanStageANewFileWithARelativePathContainingNativeDirectorySeparatorCharacters() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + string file = Path.Combine("Project", "a_file.txt"); + + Touch(repo.Info.WorkingDirectory, file, "With backward slash on Windows!"); + + 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(posixifiedPath, repo.Index[posixifiedPath].Path); + } + } + + [Fact] + public void StagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + string fullPath = Touch(scd.RootedDirectoryPath, "unit_test.txt", "some contents"); + + Assert.Throws(() => Commands.Stage(repo, fullPath)); + } + } + + [Fact] + public void StagingFileWithBadParamsThrows() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + 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 })); + } + } + + /* + * $ git status -s + * M 1/branch_file.txt + * M README + * M branch_file.txt + * D deleted_staged_file.txt + * D deleted_unstaged_file.txt + * M modified_staged_file.txt + * M modified_unstaged_file.txt + * M new.txt + * A new_tracked_file.txt + * ?? new_untracked_file.txt + * + * By passing "*" to Stage, the following files will be added/removed/updated from the index: + * - deleted_unstaged_file.txt : removed + * - modified_unstaged_file.txt : updated + * - new_untracked_file.txt : added + */ + [Theory] + [InlineData("*u*", 0)] + [InlineData("*", 0)] + [InlineData("1/*", 0)] + [InlineData("RE*", 0)] + [InlineData("d*", -1)] + [InlineData("*modified_unstaged*", 0)] + [InlineData("new_*file.txt", 1)] + public void CanStageWithPathspec(string relativePath, int expectedIndexCountVariation) + { + using (var repo = new Repository(SandboxStandardTestRepo())) + { + int count = repo.Index.Count; + + Commands.Stage(repo, relativePath); + + Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); + } + } + + [Fact] + public void CanStageWithMultiplePathspecs() + { + using (var repo = new Repository(SandboxStandardTestRepo())) + { + int count = repo.Index.Count; + + Commands.Stage(repo, new string[] { "*", "u*" }); + + Assert.Equal(count, repo.Index.Count); // 1 added file, 1 deleted file, so same count + } + } + + [Theory] + [InlineData("ignored_file.txt")] + [InlineData("ignored_folder/file.txt")] + public void CanIgnoreIgnoredPaths(string path) + { + 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.RetrieveStatus(path)); + Commands.Stage(repo, "*"); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(path)); + } + } + + [Theory] + [InlineData("ignored_file.txt")] + [InlineData("ignored_folder/file.txt")] + public void CanStageIgnoredPaths(string path) + { + 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.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 dbff37122..27a535e8e 100644 --- a/LibGit2Sharp.Tests/StashFixture.cs +++ b/LibGit2Sharp.Tests/StashFixture.cs @@ -3,6 +3,7 @@ using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { @@ -11,44 +12,46 @@ public class StashFixture : BaseFixture [Fact] public void CannotAddStashAgainstBareRepository() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + var stasher = Constants.Signature; - Assert.Throws(() => repo.Stashes.Add(stasher, "My very first stash")); + Assert.Throws(() => repo.Stashes.Add(stasher, "My very first stash", StashModifiers.Default)); } } [Fact] - public void CanAddStash() + public void CanAddAndRemoveStash() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + 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", StashOptions.IncludeUntracked); + Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashModifiers.IncludeUntracked); // Check that untracked files are deleted from working directory - Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt"))); + string untrackedFilename = "new_untracked_file.txt"; + Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, untrackedFilename))); + Assert.NotNull(stash.Untracked[untrackedFilename]); Assert.NotNull(stash); Assert.Equal("stash@{0}", stash.CanonicalName); Assert.Contains("My very first stash", stash.Message); var stashRef = repo.Refs["refs/stash"]; - Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier); + Assert.Equal(stash.WorkTree.Sha, stashRef.TargetIdentifier); - Assert.False(repo.Index.RetrieveStatus().IsDirty); + Assert.False(repo.RetrieveStatus().IsDirty); // Create extra file - string newFileFullPath = Path.Combine(repo.Info.WorkingDirectory, "stash_candidate.txt"); - File.WriteAllText(newFileFullPath, "Oh, I'm going to be stashed!\n"); + untrackedFilename = "stash_candidate.txt"; + Touch(repo.Info.WorkingDirectory, untrackedFilename, "Oh, I'm going to be stashed!\n"); - Stash secondStash = repo.Stashes.Add(stasher, "My second stash", StashOptions.IncludeUntracked); + Stash secondStash = repo.Stashes.Add(stasher, "My second stash", StashModifiers.IncludeUntracked); Assert.NotNull(stash); Assert.Equal("stash@{0}", stash.CanonicalName); @@ -58,129 +61,374 @@ public void CanAddStash() Assert.Equal("stash@{0}", repo.Stashes.First().CanonicalName); Assert.Equal("stash@{1}", repo.Stashes.Last().CanonicalName); + Assert.NotNull(secondStash.Untracked[untrackedFilename]); + // Stash history has been shifted - Assert.Equal(repo.Lookup("stash@{0}").Sha, secondStash.Target.Sha); - Assert.Equal(repo.Lookup("stash@{1}").Sha, stash.Target.Sha); + Assert.Equal(repo.Lookup("stash@{0}").Sha, secondStash.WorkTree.Sha); + Assert.Equal(repo.Lookup("stash@{1}").Sha, stash.WorkTree.Sha); + + //Remove one stash + repo.Stashes.Remove(0); + Assert.Single(repo.Stashes); + Stash newTopStash = repo.Stashes.First(); + Assert.Equal("stash@{0}", newTopStash.CanonicalName); + Assert.Equal(stash.WorkTree.Sha, newTopStash.WorkTree.Sha); + + // Stash history has been shifted + Assert.Equal(stash.WorkTree.Sha, repo.Lookup("stash").Sha); + Assert.Equal(stash.WorkTree.Sha, repo.Lookup("stash@{0}").Sha); } } [Fact] public void AddingAStashWithNoMessageGeneratesADefaultOne() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + var stasher = Constants.Signature; - Stash stash = repo.Stashes.Add(stasher); + Stash stash = repo.Stashes.Add(stasher, options: StashModifiers.Default); Assert.NotNull(stash); Assert.Equal("stash@{0}", stash.CanonicalName); - Assert.NotEmpty(stash.Target.Message); + Assert.NotEmpty(stash.WorkTree.Message); var stashRef = repo.Refs["refs/stash"]; - Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier); + Assert.Equal(stash.WorkTree.Sha, stashRef.TargetIdentifier); } } [Fact] public void AddStashWithBadParamsShouldThrows() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - Assert.Throws(() => repo.Stashes.Add(null)); + Assert.Throws(() => repo.Stashes.Add(default(Signature), options: StashModifiers.Default)); } } [Fact] public void StashingAgainstCleanWorkDirShouldReturnANullStash() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + var stasher = Constants.Signature; - Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashOptions.IncludeUntracked); + Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashModifiers.IncludeUntracked); Assert.NotNull(stash); //Stash against clean working directory - Assert.Null(repo.Stashes.Add(stasher)); + Assert.Null(repo.Stashes.Add(stasher, options: StashModifiers.Default)); } } [Fact] public void CanStashWithoutOptions() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + var stasher = Constants.Signature; - var untrackedFilePath = Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt"); - File.WriteAllText(untrackedFilePath, "I'm untracked\n"); + const string untracked = "new_untracked_file.txt"; + Touch(repo.Info.WorkingDirectory, untracked, "I'm untracked\n"); - string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt"); - File.WriteAllText(stagedfilePath, "I'm staged\n"); - repo.Index.Stage(stagedfilePath); + const string staged = "staged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, staged, "I'm staged\n"); + Commands.Stage(repo, staged); - Stash stash = repo.Stashes.Add(stasher, "Stash with default options"); + 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_file_path.txt")); + 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("new_untracked_file.txt")); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(untracked)); + Assert.Null(stash.Untracked); } } [Fact] public void CanStashAndKeepIndex() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - var stasher = DummySignature; + var stasher = Constants.Signature; - string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt"); - File.WriteAllText(stagedfilePath, "I'm staged\n"); - repo.Index.Stage(stagedfilePath); + const string filename = "staged_file_path.txt"; + Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n"); + Commands.Stage(repo, filename); - Stash stash = repo.Stashes.Add(stasher, "This stash wil keep index", StashOptions.KeepIndex); + Stash stash = repo.Stashes.Add(stasher, "This stash will keep index", StashModifiers.KeepIndex); Assert.NotNull(stash); - Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus("staged_file_path.txt")); + Assert.NotNull(stash.Index[filename]); + Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename)); + Assert.Null(stash.Untracked); } } [Fact] public void CanStashIgnoredFiles() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - string gitIgnoreFilePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(gitIgnoreFilePath, "ignored_file.txt"); - repo.Index.Stage(gitIgnoreFilePath); + const string gitIgnore = ".gitignore"; + const string ignoredFilename = "ignored_file.txt"; + + Touch(repo.Info.WorkingDirectory, gitIgnore, ignoredFilename); + Commands.Stage(repo, gitIgnore); repo.Commit("Modify gitignore", Constants.Signature, Constants.Signature); - string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, "ignored_file.txt"); - File.WriteAllText(ignoredFilePath, "I'm ignored\n"); + Touch(repo.Info.WorkingDirectory, ignoredFilename, "I'm ignored\n"); - Assert.True(repo.Ignore.IsPathIgnored("ignored_file.txt")); + Assert.True(repo.Ignore.IsPathIgnored(ignoredFilename)); - var stasher = DummySignature; - repo.Stashes.Add(stasher, "This stash includes ignore files", StashOptions.IncludeIgnored); + var stasher = Constants.Signature; + var stash = repo.Stashes.Add(stasher, "This stash includes ignore files", StashModifiers.IncludeIgnored); - //TODO : below assertion doesn't pass. Bug? - //Assert.False(File.Exists(ignoredFilePath)); + Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, ignoredFilename))); var blob = repo.Lookup("stash^3:ignored_file.txt"); Assert.NotNull(blob); + Assert.NotNull(stash.Untracked[ignoredFilename]); + } + } + + [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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Stashes.Remove(badIndex)); + } + } + + [Fact] + public void CanGetStashByIndexer() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + var stasher = Constants.Signature; + const string firstStashMessage = "My very first stash"; + const string secondStashMessage = "My second stash"; + const string thirdStashMessage = "My third stash"; + + // Create first stash + Stash firstStash = repo.Stashes.Add(stasher, firstStashMessage, StashModifiers.IncludeUntracked); + Assert.NotNull(firstStash); + + // Create second stash + Touch(repo.Info.WorkingDirectory, "stash_candidate.txt", "Oh, I'm going to be stashed!\n"); + + Stash secondStash = repo.Stashes.Add(stasher, secondStashMessage, StashModifiers.IncludeUntracked); + Assert.NotNull(secondStash); + + // Create third stash + Touch(repo.Info.WorkingDirectory, "stash_candidate_again.txt", "Oh, I'm going to be stashed!\n"); + + + Stash thirdStash = repo.Stashes.Add(stasher, thirdStashMessage, StashModifiers.IncludeUntracked); + Assert.NotNull(thirdStash); + + // Get by indexer + Assert.Equal(3, repo.Stashes.Count()); + Assert.Equal("stash@{0}", repo.Stashes[0].CanonicalName); + Assert.Contains(thirdStashMessage, repo.Stashes[0].Message); + Assert.Equal(thirdStash.WorkTree, repo.Stashes[0].WorkTree); + Assert.Equal("stash@{1}", repo.Stashes[1].CanonicalName); + Assert.Contains(secondStashMessage, repo.Stashes[1].Message); + Assert.Equal(secondStash.WorkTree, repo.Stashes[1].WorkTree); + Assert.Equal("stash@{2}", repo.Stashes[2].CanonicalName); + Assert.Contains(firstStashMessage, repo.Stashes[2].Message); + Assert.Equal(firstStash.WorkTree, repo.Stashes[2].WorkTree); + } + } + + [Theory] + [InlineData(-1)] + [InlineData(-42)] + public void GettingStashWithBadIndexThrows(int badIndex) + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Assert.Throws(() => repo.Stashes[badIndex]); + } + } + + [Theory] + [InlineData(28)] + [InlineData(42)] + public void GettingAStashThatDoesNotExistReturnsNull(int bigIndex) + { + 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 cd6568952..698639aa4 100644 --- a/LibGit2Sharp.Tests/StatusFixture.cs +++ b/LibGit2Sharp.Tests/StatusFixture.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Text; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; @@ -12,155 +13,342 @@ 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.NewInWorkdir)] + [InlineData(StatusShowOption.WorkDirOnly, FileStatus.NewInWorkdir)] + [InlineData(StatusShowOption.IndexOnly, FileStatus.Nonexistent)] + public void CanLimitStatusToWorkDirOnly(StatusShowOption show, FileStatus expected) + { + var clone = SandboxStandardTestRepo(); + + using (var repo = new Repository(clone)) + { + Touch(repo.Info.WorkingDirectory, "file.txt", "content"); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { Show = show }); + Assert.Equal(expected, status["file.txt"].State); + } + } + + [Theory] + [InlineData(StatusShowOption.IndexAndWorkDir, FileStatus.NewInIndex)] + [InlineData(StatusShowOption.WorkDirOnly, FileStatus.Nonexistent)] + [InlineData(StatusShowOption.IndexOnly, FileStatus.NewInIndex)] + public void CanLimitStatusToIndexOnly(StatusShowOption show, FileStatus expected) + { + var clone = SandboxStandardTestRepo(); + + using (var repo = new Repository(clone)) + { + Touch(repo.Info.WorkingDirectory, "file.txt", "content"); + Commands.Stage(repo, "file.txt"); + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { Show = show }); + Assert.Equal(expected, status["file.txt"].State); + } + } + + + [Theory] + [InlineData("file")] + [InlineData("file.txt")] + [InlineData("$file")] + [InlineData("$file.txt")] + [InlineData("$dir/file")] + [InlineData("$dir/file.txt")] + [InlineData("#file")] + [InlineData("#file.txt")] + [InlineData("#dir/file")] + [InlineData("#dir/file.txt")] + [InlineData("^file")] + [InlineData("^file.txt")] + [InlineData("^dir/file")] + [InlineData("^dir/file.txt")] + [InlineData("!file")] + [InlineData("!file.txt")] + [InlineData("!dir/file")] + [InlineData("!dir/file.txt")] + [InlineData("file!")] + [InlineData("file!.txt")] + [InlineData("dir!/file")] + [InlineData("dir!/file.txt")] + public void CanRetrieveTheStatusOfAnUntrackedFile(string filePath) + { + var clone = SandboxStandardTestRepo(); + + using (var repo = new Repository(clone)) + { + Touch(repo.Info.WorkingDirectory, filePath, "content"); + + 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) { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + 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]); + 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.Single()); - Assert.Equal("modified_unstaged_file.txt", status.Modified.Single()); - Assert.Equal("deleted_unstaged_file.txt", status.Missing.Single()); - Assert.Equal("new_tracked_file.txt", status.Added.Single()); - Assert.Equal(file, status.Staged.Single()); - Assert.Equal("deleted_staged_file.txt", status.Removed.Single()); + Assert.Equal("new_untracked_file.txt", status.Untracked.Select(s => s.FilePath).Single()); + Assert.Equal("modified_unstaged_file.txt", status.Modified.Select(s => s.FilePath).Single()); + Assert.Equal("deleted_unstaged_file.txt", status.Missing.Select(s => s.FilePath).Single()); + Assert.Equal("new_tracked_file.txt", status.Added.Select(s => s.FilePath).Single()); + Assert.Equal(file, status.Staged.Select(s => s.FilePath).Single()); + Assert.Equal("deleted_staged_file.txt", status.Removed.Select(s => s.FilePath).Single()); 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]); + 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.Single()); - Assert.Equal(new[] { file, "modified_unstaged_file.txt" }, status2.Modified); - Assert.Equal("deleted_unstaged_file.txt", status2.Missing.Single()); - Assert.Equal("new_tracked_file.txt", status2.Added.Single()); - Assert.Equal(file, status2.Staged.Single()); - Assert.Equal("deleted_staged_file.txt", status2.Removed.Single()); + Assert.Equal("new_untracked_file.txt", status2.Untracked.Select(s => s.FilePath).Single()); + Assert.Equal(new[] { file, "modified_unstaged_file.txt" }, status2.Modified.Select(s => s.FilePath)); + Assert.Equal("deleted_unstaged_file.txt", status2.Missing.Select(s => s.FilePath).Single()); + Assert.Equal("new_tracked_file.txt", status2.Added.Select(s => s.FilePath).Single()); + Assert.Equal(file, status2.Staged.Select(s => s.FilePath).Single()); + Assert.Equal("deleted_staged_file.txt", status2.Removed.Select(s => s.FilePath).Single()); } } [Fact] - public void CanRetrieveTheStatusOfANewRepository() + public void CanRetrieveTheStatusOfRenamedFilesInWorkDir() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Touch(repo.Info.WorkingDirectory, "old_name.txt", + "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" + + "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"); + + 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.RetrieveStatus( + new StatusOptions() + { + DetectRenamesInIndex = true, + DetectRenamesInWorkDir = true + }); + + Assert.Equal(FileStatus.NewInIndex | FileStatus.RenamedInWorkdir, status["rename_target.txt"].State); + Assert.Equal(100, status["rename_target.txt"].IndexToWorkDirRenameDetails.Similarity); + } + } - using (Repository repo = Repository.Init(scd.DirectoryPath)) + [Fact] + public void CanRetrieveTheStatusOfRenamedFilesInIndex() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.NotNull(status); - Assert.Equal(0, status.Count()); - Assert.False(status.IsDirty); + File.Move( + Path.Combine(repo.Info.WorkingDirectory, "1.txt"), + Path.Combine(repo.Info.WorkingDirectory, "rename_target.txt")); + + Commands.Stage(repo, "1.txt"); + Commands.Stage(repo, "rename_target.txt"); - 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()); + RepositoryStatus status = repo.RetrieveStatus(); + + Assert.Equal(FileStatus.RenamedInIndex, status["rename_target.txt"].State); + Assert.Equal(100, status["rename_target.txt"].HeadToIndexRenameDetails.Similarity); } } [Fact] - public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() + public void CanDetectedVariousKindsOfRenaming() + { + string path = InitNewRepository(); + using (var repo = new Repository(path)) + { + Touch(repo.Info.WorkingDirectory, "file.txt", + "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" + + "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"); + + Commands.Stage(repo, "file.txt"); + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + + File.Move(Path.Combine(repo.Info.WorkingDirectory, "file.txt"), + Path.Combine(repo.Info.WorkingDirectory, "renamed.txt")); + + var opts = new StatusOptions + { + DetectRenamesInIndex = true, + DetectRenamesInWorkDir = true + }; + + RepositoryStatus status = repo.RetrieveStatus(opts); + + // This passes as expected + Assert.Equal(FileStatus.RenamedInWorkdir, status.Single().State); + + Commands.Stage(repo, "file.txt"); + Commands.Stage(repo, "renamed.txt"); + + 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.RetrieveStatus(opts); + + Assert.Equal(FileStatus.RenamedInWorkdir | FileStatus.RenamedInIndex, + status.Single().State); + } + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void CanRetrieveTheStatusOfANewRepository(bool includeUnaltered) { - // Initialize a new repository - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - const string directoryName = "directory"; - const string fileName = "Testfile.txt"; + using (var repo = new Repository(repoPath)) + { + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { IncludeUnaltered = includeUnaltered }); + Assert.NotNull(status); + Assert.Empty(status); + Assert.False(status.IsDirty); - // Create a file and insert some content - string directoryPath = Path.Combine(scd.RootedDirectoryPath, directoryName); - string filePath = Path.Combine(directoryPath, fileName); + Assert.Empty(status.Untracked); + Assert.Empty(status.Modified); + Assert.Empty(status.Missing); + Assert.Empty(status.Added); + Assert.Empty(status.Staged); + Assert.Empty(status.Removed); + } + } - Directory.CreateDirectory(directoryPath); - File.WriteAllText(filePath, "Anybody out there?"); + [Fact] + public void RetrievingTheStatusOfARepositoryReturnsGitPaths() + { + // Build relative path + string relFilePath = Path.Combine("directory", "Testfile.txt"); // Open the repository - using (Repository repo = Repository.Init(scd.DirectoryPath)) + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) { + Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); + // Add the file to the index - repo.Index.Stage(filePath); + 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(Path.Combine(directoryName, fileName), statusEntry.FilePath); + Assert.Equal(relFilePath.Replace('\\', '/'), statusEntry.FilePath); - Assert.Equal(statusEntry.FilePath, repoStatus.Added.Single()); + Assert.Equal(statusEntry.FilePath, repoStatus.Added.Select(s => s.FilePath).Single()); } } [Fact] public void RetrievingTheStatusOfAnEmptyRepositoryHonorsTheGitIgnoreDirectives() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (Repository repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { const string relativePath = "look-ma.txt"; - string fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); - File.WriteAllText(fullFilePath, "I'm going to be ignored!"); + Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!"); + + 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.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); + + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); + Assert.Equal(new[] { relativePath }, newStatus.Ignored.Select(s => s.FilePath)); + } + } + + [Fact] + public void RetrievingTheStatusWithoutIncludeIgnoredIgnoresButDoesntInclude() + { + string repoPath = InitNewRepository(); - RepositoryStatus status = repo.Index.RetrieveStatus(); - Assert.Equal(new[] { relativePath }, status.Untracked); + 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)); - string gitignorePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(gitignorePath, "*.txt" + Environment.NewLine); + Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(".gitignore", newStatus.Untracked.Single()); + RepositoryStatus newStatus = repo.RetrieveStatus(opt); + Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); - Assert.Equal(new[] { relativePath }, newStatus.Ignored); + Assert.False(newStatus.Ignored.Any()); } } [Fact] public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { string relativePath = Path.Combine("1", "look-ma.txt"); - string fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); - File.WriteAllText(fullFilePath, "I'm going to be ignored!"); + Touch(repo.Info.WorkingDirectory, relativePath, "I'm going to be ignored!"); /* * $ git status --ignored @@ -193,12 +381,12 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() * # new_untracked_file.txt */ - RepositoryStatus status = repo.Index.RetrieveStatus(); + RepositoryStatus status = repo.RetrieveStatus(); - Assert.Equal(new[]{relativePath, "new_untracked_file.txt"}, status.Untracked); + relativePath = relativePath.Replace('\\', '/'); + Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, status.Untracked.Select(s => s.FilePath)); - string gitignorePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); - File.WriteAllText(gitignorePath, "*.txt" + Environment.NewLine); + Touch(repo.Info.WorkingDirectory, ".gitignore", "*.txt" + Environment.NewLine); /* * $ git status --ignored @@ -235,64 +423,241 @@ public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() * # new_untracked_file.txt */ - RepositoryStatus newStatus = repo.Index.RetrieveStatus(); - Assert.Equal(".gitignore", newStatus.Untracked.Single()); + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.Equal(".gitignore", newStatus.Untracked.Select(s => s.FilePath).Single()); + + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); + Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored.Select(s => s.FilePath)); + } + } + + [Theory] + [InlineData(true, FileStatus.Unaltered, FileStatus.Unaltered)] + [InlineData(false, FileStatus.DeletedFromWorkdir, FileStatus.NewInWorkdir)] + public void RetrievingTheStatusOfAFilePathHonorsTheIgnoreCaseConfigurationSetting( + bool shouldIgnoreCase, + FileStatus expectedLowercaseFileStatus, + FileStatus expectedUppercaseFileStatus + ) + { + string lowercasePath; + const string lowercaseFileName = "plop"; + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + repo.Config.Set("core.ignorecase", shouldIgnoreCase); + + lowercasePath = Touch(repo.Info.WorkingDirectory, lowercaseFileName); + + Commands.Stage(repo, lowercaseFileName); + repo.Commit("initial", Constants.Signature, Constants.Signature); + } + + using (var repo = new Repository(repoPath)) + { + const string uppercaseFileName = "PLOP"; + + string uppercasePath = Path.Combine(repo.Info.WorkingDirectory, uppercaseFileName); + + //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); + + Assert.Equal(expectedLowercaseFileStatus, repo.RetrieveStatus(lowercaseFileName)); + Assert.Equal(expectedUppercaseFileStatus, repo.RetrieveStatus(uppercaseFileName)); - Assert.Equal(FileStatus.Ignored, repo.Index.RetrieveStatus(relativePath)); - Assert.Equal(new[] { relativePath, "new_untracked_file.txt" }, newStatus.Ignored); + AssertStatus(shouldIgnoreCase, expectedLowercaseFileStatus, repo, uppercasePath.ToLowerInvariant()); + AssertStatus(shouldIgnoreCase, expectedUppercaseFileStatus, repo, uppercasePath.ToUpperInvariant()); + } + } + + private static void AssertStatus(bool shouldIgnoreCase, FileStatus expectedFileStatus, IRepository repo, string path) + { + try + { + Assert.Equal(expectedFileStatus, repo.RetrieveStatus(path)); + } + catch (ArgumentException) + { + Assert.False(shouldIgnoreCase); } } [Fact] - public void RetrievingTheStatusOfAnAmbiguousFileThrows() + public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectivesThroughoutDirectories() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - string relativePath = Path.Combine("1", "ambiguous1.txt"); - string fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); - File.WriteAllText(fullFilePath, "I don't like brackets."); + Touch(repo.Info.WorkingDirectory, "bin/look-ma.txt", "I'm going to be ignored!"); + Touch(repo.Info.WorkingDirectory, "bin/what-about-me.txt", "Huh?"); + + const string gitIgnore = ".gitignore"; + Touch(repo.Info.WorkingDirectory, gitIgnore, "bin"); + + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/look-ma.txt")); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/what-about-me.txt")); + + RepositoryStatus newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + Assert.Equal(new[] { "bin/" }, newStatus.Ignored.Select(s => s.FilePath)); - relativePath = Path.Combine("1", "ambiguous[1].txt"); - fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); - File.WriteAllText(fullFilePath, "Brackets all the way."); + var sb = new StringBuilder(); + sb.AppendLine("bin/*"); + sb.AppendLine("!bin/w*"); + Touch(repo.Info.WorkingDirectory, gitIgnore, sb.ToString()); - Assert.Throws(() => repo.Index.RetrieveStatus(relativePath)); + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus("bin/look-ma.txt")); + Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus("bin/what-about-me.txt")); + + newStatus = repo.RetrieveStatus(new StatusOptions { IncludeIgnored = true }); + + 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)); } } - [Theory] - [InlineData(true, FileStatus.Unaltered, FileStatus.Unaltered)] - [InlineData(false, FileStatus.Missing, FileStatus.Untracked)] - public void RetrievingTheStatusOfAFilePathHonorsTheIgnoreCaseConfigurationSetting( - bool shouldIgnoreCase, - FileStatus expectedlowerCasedFileStatus, - FileStatus expectedCamelCasedFileStatus - ) + [Fact] + public void CanRetrieveStatusOfFilesInSubmodule() + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + string[] expected = new string[] { + ".gitmodules", + "sm_changed_file", + "sm_changed_head", + "sm_changed_index", + "sm_changed_untracked_file", + "sm_missing_commits" + }; + + RepositoryStatus status = repo.RetrieveStatus(); + Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray()); + } + } + + [Fact] + public void CanExcludeStatusOfFilesInSubmodule() + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + string[] expected = new string[] { + ".gitmodules", + }; + + RepositoryStatus status = repo.RetrieveStatus(new StatusOptions() { ExcludeSubmodules = true }); + Assert.Equal(expected, status.Modified.Select(x => x.FilePath).ToArray()); + } + } + + [Fact] + public void CanRetrieveTheStatusOfARelativeWorkingDirectory() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + 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); - string lowerCasedPath; + 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); + } + } - using (Repository repo = Repository.Init(scd.DirectoryPath)) + [Fact] + public void CanRetrieveTheStatusOfMultiplePathSpec() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) { - repo.Config.Set("core.ignorecase", shouldIgnoreCase); + 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()); + } + } - lowerCasedPath = Path.Combine(repo.Info.WorkingDirectory, "plop"); + [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); + } + } - File.WriteAllText(lowerCasedPath, string.Empty); + [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 }); - repo.Index.Stage(lowerCasedPath); - repo.Commit("initial", DummySignature, DummySignature); + Assert.Equal(unalteredPaths.Length, status.Unaltered.Count()); + Assert.Equal(unalteredPaths, status.Unaltered.OrderBy(s => s.FilePath, StringComparer.OrdinalIgnoreCase).Select(s => s.FilePath).ToArray()); } + } - using (var repo = new Repository(scd.DirectoryPath)) + [Fact] + public void UnalteredFilesDontMarkIndexAsDirty() + { + var path = SandboxStandardTestRepo(); + + using (var repo = new Repository(path)) { - string camelCasedPath = Path.Combine(repo.Info.WorkingDirectory, "Plop"); - File.Move(lowerCasedPath, camelCasedPath); + repo.Reset(ResetMode.Hard); + repo.RemoveUntrackedFiles(); - Assert.Equal(expectedlowerCasedFileStatus, repo.Index.RetrieveStatus("plop")); - Assert.Equal(expectedCamelCasedFileStatus, repo.Index.RetrieveStatus("Plop")); + 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 new file mode 100644 index 000000000..2d7f04e6d --- /dev/null +++ b/LibGit2Sharp.Tests/SubmoduleFixture.cs @@ -0,0 +1,337 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class SubmoduleFixture : BaseFixture + { + [Fact] + public void RetrievingSubmoduleForNormalDirectoryReturnsNull() + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules["just_a_dir"]; + Assert.Null(submodule); + } + } + + [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)] + [InlineData("sm_changed_head", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirModified)] + [InlineData("sm_changed_index", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesIndexDirty)] + [InlineData("sm_changed_untracked_file", SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir | SubmoduleStatus.WorkDirFilesUntracked)] + [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)] + [InlineData("sm_branch_only", null)] + public void CanRetrieveTheStatusOfASubmodule(string name, SubmoduleStatus? expectedStatus) + { + 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); + + var status = submodule.RetrieveStatus(); + Assert.Equal(expectedStatus, status); + } + } + + [Theory] + [InlineData("sm_added_and_uncommited", null, "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] + [InlineData("sm_changed_file", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] + [InlineData("sm_changed_head", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "3d9386c507f6b093471a3e324085657a3c2b4247")] + [InlineData("sm_changed_index", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] + [InlineData("sm_changed_untracked_file", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] + [InlineData("sm_gitmodules_only", null, null, null)] + [InlineData("sm_missing_commits", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "5e4963595a9774b90524d35a807169049de8ccad")] + [InlineData("sm_unchanged", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0", "480095882d281ed676fe5b863569520e54a7d5c0")] + public void CanRetrieveTheCommitIdsOfASubmodule(string name, string headId, string indexId, string workDirId) + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[name]; + Assert.NotNull(submodule); + AssertBelongsToARepository(repo, submodule); + Assert.Equal(name, submodule.Name); + + Assert.Equal((ObjectId)headId, submodule.HeadCommitId); + Assert.Equal((ObjectId)indexId, submodule.IndexCommitId); + Assert.Equal((ObjectId)workDirId, submodule.WorkDirCommitId); + + AssertEntryId((ObjectId)headId, repo.Head[name], c => c.Target.Id); + AssertEntryId((ObjectId)indexId, repo.Index[name], i => i.Id); + } + } + + private static void AssertEntryId(ObjectId expected, T entry, Func selector) + { + Assert.Equal(expected, ReferenceEquals(entry, null) ? null : selector(entry)); + } + + [Fact] + public void CanEnumerateRepositorySubmodules() + { + var expectedSubmodules = new[] + { + "sm_added_and_uncommited", + "sm_changed_file", + "sm_changed_head", + "sm_changed_index", + "sm_changed_untracked_file", + "sm_gitmodules_only", + "sm_missing_commits", + "sm_unchanged", + }; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodules = repo.Submodules.OrderBy(s => s.Name, StringComparer.Ordinal); + + Assert.Equal(expectedSubmodules, submodules.Select(s => s.Name).ToArray()); + Assert.Equal(expectedSubmodules, submodules.Select(s => s.Path).ToArray()); + Assert.Equal(Enumerable.Repeat("../submodule_target_wd", expectedSubmodules.Length).ToArray(), + submodules.Select(s => s.Url).ToArray()); + } + } + + [Theory] + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStage(string submodulePath) + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submodulePath]; + Assert.NotNull(submodule); + + var statusBefore = submodule.RetrieveStatus(); + Assert.Equal(SubmoduleStatus.WorkDirModified, statusBefore & SubmoduleStatus.WorkDirModified); + + Commands.Stage(repo, submodulePath); + + var statusAfter = submodule.RetrieveStatus(); + Assert.Equal(SubmoduleStatus.IndexModified, statusAfter & SubmoduleStatus.IndexModified); + } + } + + [Theory] + [InlineData("sm_changed_head")] + [InlineData("sm_changed_head/")] + public void CanStageChangeInSubmoduleViaIndexStageWithOtherPaths(string submodulePath) + { + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submodulePath]; + Assert.NotNull(submodule); + + var statusBefore = submodule.RetrieveStatus(); + Assert.Equal(SubmoduleStatus.WorkDirModified, statusBefore & SubmoduleStatus.WorkDirModified); + + Touch(repo.Info.WorkingDirectory, "new-file.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 c3dfdd1f6..9f125806c 100644 --- a/LibGit2Sharp.Tests/TagFixture.cs +++ b/LibGit2Sharp.Tests/TagFixture.cs @@ -4,15 +4,16 @@ using LibGit2Sharp.Core; using LibGit2Sharp.Tests.TestHelpers; using Xunit; +using Xunit.Extensions; namespace LibGit2Sharp.Tests { public class TagFixture : BaseFixture { - private readonly string[] expectedTags = new[] { "e90810b", "lw", "point_to_blob", "test", }; + 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,8 +21,8 @@ public class TagFixture : BaseFixture [Fact] public void CanAddALightWeightTagFromSha() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", commitE90810BSha); Assert.NotNull(newTag); @@ -33,8 +34,8 @@ public void CanAddALightWeightTagFromSha() [Fact] public void CanAddALightWeightTagFromAGitObject() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { GitObject obj = repo.Lookup(commitE90810BSha); @@ -48,8 +49,8 @@ public void CanAddALightWeightTagFromAGitObject() [Fact] public void CanAddALightWeightTagFromAbbreviatedSha() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", commitE90810BSha.Substring(0, 17)); Assert.NotNull(newTag); @@ -60,8 +61,8 @@ public void CanAddALightWeightTagFromAbbreviatedSha() [Fact] public void CanAddALightweightTagFromABranchName() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", "refs/heads/master"); Assert.False(newTag.IsAnnotated); @@ -72,8 +73,8 @@ public void CanAddALightweightTagFromABranchName() [Fact] public void CanAddALightweightTagFromARevparseSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("i_am_lightweight", "master^1^2"); Assert.False(newTag.IsAnnotated); @@ -85,8 +86,8 @@ public void CanAddALightweightTagFromARevparseSpec() [Fact] public void CanAddAndOverwriteALightweightTag() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("e90810b", commitE90810BSha, true); Assert.NotNull(newTag); @@ -97,15 +98,15 @@ public void CanAddAndOverwriteALightweightTag() [Fact] public void CanAddATagWithNameContainingASlash() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string lwTagName = "i/am/deep"; Tag lwTag = repo.Tags.Add(lwTagName, commitE90810BSha); 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,15 +114,15 @@ 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { repo.ApplyTag("i/am/deep"); Assert.Throws(() => repo.ApplyTag("i/am/deep/rooted")); @@ -132,8 +133,8 @@ public void CreatingATagWithNameMatchingAnAlreadyExistingReferenceHierarchyThrow [Fact] public void CanAddAnAnnotatedTagFromABranchName() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", "refs/heads/master", signatureTim, "a new tag"); Assert.True(newTag.IsAnnotated); @@ -144,8 +145,8 @@ public void CanAddAnAnnotatedTagFromABranchName() [Fact] public void CanAddAnAnnotatedTagFromSha() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", tagTestSha, signatureTim, "a new tag"); Assert.NotNull(newTag); @@ -158,12 +159,12 @@ public void CanAddAnAnnotatedTagFromSha() [Fact] public void CanAddAnAnnotatedTagFromObject() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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,8 +174,8 @@ public void CanAddAnAnnotatedTagFromObject() [Fact] public void CanAddAnAnnotatedTagFromARevparseSpec() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("unit_test", "master^1^2", signatureTim, "a new tag"); Assert.NotNull(newTag); @@ -187,8 +188,8 @@ public void CanAddAnAnnotatedTagFromARevparseSpec() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L359) public void CanAddAnAnnotatedTagWithAnEmptyMessage() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.ApplyTag("empty-annotated-tag", signatureNtk, string.Empty); Assert.NotNull(newTag); @@ -200,8 +201,8 @@ public void CanAddAnAnnotatedTagWithAnEmptyMessage() [Fact] public void CanAddAndOverwriteAnAnnotatedTag() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add("e90810b", tagTestSha, signatureTim, "a new tag", true); Assert.NotNull(newTag); @@ -215,8 +216,8 @@ public void CreatingAnAnnotatedTagIsDeterministic() const string tagName = "nullTAGen"; const string tagMessage = "I've been tagged!"; - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag newTag = repo.Tags.Add(tagName, commitE90810BSha, signatureNtk, tagMessage); Assert.Equal(commitE90810BSha, newTag.Target.Sha); @@ -230,11 +231,11 @@ public void CreatingAnAnnotatedTagIsDeterministic() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L32) public void CreatingATagInAEmptyRepositoryThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.ApplyTag("mynotag")); + Assert.Throws(() => repo.ApplyTag("mynotag")); } } @@ -242,11 +243,12 @@ public void CreatingATagInAEmptyRepositoryThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L37) public void CreatingATagForHeadInAEmptyRepositoryThrows() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Assert.Throws(() => repo.ApplyTag("mytaghead", "HEAD")); + Assert.Throws(() => repo.ApplyTag("mytaghead", "HEAD")); + Assert.Throws(() => repo.ApplyTag("mytaghead")); } } @@ -254,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")); } } @@ -264,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)); } } @@ -274,8 +278,8 @@ public void CreatingATagForAnUnknowObjectIdThrows() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L48) public void CanAddATagForImplicitHead() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.ApplyTag("mytag"); Assert.NotNull(tag); @@ -287,12 +291,32 @@ public void CanAddATagForImplicitHead() } } + [Fact] + public void CanAddATagForImplicitHeadInDetachedState() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Commands.Checkout(repo, repo.Head.Tip); + + Assert.True(repo.Info.IsHeadDetached); + + Tag tag = repo.ApplyTag("mytag"); + Assert.NotNull(tag); + + Assert.Equal(repo.Head.Tip.Id, tag.Target.Id); + + Tag retrievedTag = repo.Tags[tag.CanonicalName]; + Assert.Equal(retrievedTag, tag); + } + } + [Fact] // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L87) public void CreatingADuplicateTagThrows() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { repo.ApplyTag("mytag"); @@ -304,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")); @@ -318,8 +343,8 @@ public void CreatingATagWithANonValidNameShouldFail() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L101) public void CanAddATagUsingHead() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.ApplyTag("mytag", "HEAD"); Assert.NotNull(tag); @@ -334,8 +359,8 @@ public void CanAddATagUsingHead() [Fact] public void CanAddATagPointingToATree() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Commit headCommit = repo.Head.Tip; Tree tree = headCommit.Tree; @@ -346,18 +371,37 @@ 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]); + } + } + + [Fact] + public void CanReadTagWithoutTagger() + { + // Not all tags have a tagger. + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + Tag tag = repo.Tags["tag_without_tagger"]; + + Assert.True(tag.IsAnnotated); + Assert.NotNull(tag.Target); + Assert.Null(tag.Annotation.Tagger); + + Tree tree = repo.Lookup("581f9824ecaf824221bd36edf5430f2739a7c4f5"); + Assert.NotNull(tree); + + Assert.Equal(tree.Id, tag.Target.Id); } } [Fact] public void CanAddATagPointingToABlob() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { - Commit headCommit = repo.Head.Tip; - Blob blob = headCommit.Tree.Blobs.First(); + var blob = repo.Lookup("a823312"); Tag tag = repo.ApplyTag("blob-tag", blob.Sha); Assert.NotNull(tag); @@ -365,15 +409,15 @@ 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag annotatedTag = repo.Tags["e90810b"]; TagAnnotation annotation = annotatedTag.Annotation; @@ -385,15 +429,15 @@ 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() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag annotatedTag = repo.Tags["e90810b"]; TagAnnotation annotation = annotatedTag.Annotation; @@ -404,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")); } @@ -420,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")); } @@ -429,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")); } @@ -438,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")); } @@ -447,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")); } @@ -456,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)); } @@ -474,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")); } @@ -483,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")); } @@ -492,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")); @@ -502,8 +556,8 @@ public void AddTagWithNullTargetThrows() [Fact] public void CanRemoveATagThroughItsName() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { repo.Tags.Remove("e90810b"); } @@ -512,8 +566,8 @@ public void CanRemoveATagThroughItsName() [Fact] public void CanRemoveATagThroughItsCanonicalName() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { repo.Tags.Remove("refs/tags/e90810b"); } @@ -522,8 +576,8 @@ public void CanRemoveATagThroughItsCanonicalName() [Fact] public void CanRemoveATag() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { Tag tag = repo.Tags["e90810b"]; repo.Tags.Remove(tag); @@ -533,8 +587,8 @@ public void CanRemoveATag() [Fact] public void ARemovedTagCannotBeLookedUp() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) { const string tagName = "e90810b"; @@ -546,18 +600,18 @@ public void ARemovedTagCannotBeLookedUp() [Fact] public void RemovingATagDecreasesTheTagsCount() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) + 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); } @@ -567,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]); @@ -587,11 +643,12 @@ 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, repo.Tags.Select(t => t.Name).ToArray()); + Assert.Equal(expectedTags, SortedTags(repo.Tags, t => t.FriendlyName)); - Assert.Equal(4, repo.Tags.Count()); + Assert.Equal(5, repo.Tags.Count()); } } @@ -599,38 +656,24 @@ public void CanListTags() // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L24) public void CanListAllTagsInAEmptyRepository() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string repoPath = InitNewRepository(); - using (var repo = Repository.Init(scd.DirectoryPath)) + using (var repo = new Repository(repoPath)) { - Assert.True(repo.Info.IsHeadOrphaned); - Assert.Equal(0, repo.Tags.Count()); - } - } - - [Fact] - // Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L165) - public void ListAllTagsShouldOutputThemInAnOrderedWay() - { - using (var repo = new Repository(BareTestRepoPath)) - { - List tagNames = repo.Tags.Select(t => t.Name).ToList(); - - List sortedTags = expectedTags.ToList(); - sortedTags.Sort(); - - Assert.Equal(sortedTags, tagNames); + Assert.True(repo.Info.IsHeadUnborn); + 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); @@ -641,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)); @@ -659,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); @@ -679,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]; }); } @@ -688,10 +734,45 @@ 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 4ac054072..e9429d562 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -1,9 +1,12 @@ -using System; +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Text.RegularExpressions; -using LibGit2Sharp.Core; using Xunit; namespace LibGit2Sharp.Tests.TestHelpers @@ -12,6 +15,15 @@ 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 @@ -21,49 +33,137 @@ static BaseFixture() public static string BareTestRepoPath { get; private set; } public static string StandardTestRepoWorkingDirPath { get; private set; } public static string StandardTestRepoPath { get; private set; } + public static string ShallowTestRepoPath { get; private set; } public static string MergedTestRepoWorkingDirPath { get; private set; } + public static string MergeTestRepoWorkingDirPath { get; private set; } + 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 readonly Signature DummySignature = new Signature("Author N. Ame", "him@there.com", TruncateSubSeconds(DateTimeOffset.Now)); + 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() { - var source = new DirectoryInfo(@"../../Resources"); - ResourcesDirectory = new DirectoryInfo(string.Format(@"Resources/{0}", Guid.NewGuid())); - var parent = new DirectoryInfo(@"Resources"); + IsFileSystemCaseSensitive = IsFileSystemCaseSensitiveInternal(); - if (parent.Exists) + var resourcesPath = Environment.GetEnvironmentVariable("LIBGIT2SHARP_RESOURCES"); + + 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-" + Path.GetRandomFileName()); + + if (Directory.Exists(mixedPath)) + { + Directory.Delete(mixedPath); + } + + Directory.CreateDirectory(mixedPath); + bool isInsensitive = Directory.Exists(mixedPath.ToLowerInvariant()); + + Directory.Delete(mixedPath); - // The test repo under source control has its .git folder renamed to dot_git to avoid confusing git, - // so we need to rename it back to .git in our copy under the target folder - string tempDotGit = Path.Combine(StandardTestRepoWorkingDirPath, "dot_git"); - Directory.Move(tempDotGit, StandardTestRepoPath); - tempDotGit = Path.Combine(MergedTestRepoWorkingDirPath, "dot_git"); - Directory.Move(tempDotGit, Path.Combine(MergedTestRepoWorkingDirPath, ".git")); + return !isInsensitive; } protected void CreateCorruptedDeadBeefHead(string repoPath) { const string deadbeef = "deadbeef"; - string headPath = string.Format("{0}refs/heads/{1}", repoPath, deadbeef); - File.WriteAllText(headPath, string.Format("{0}{0}{0}{0}{0}\n", deadbeef)); + string headPath = string.Format("refs/heads/{0}", deadbeef); + + Touch(repoPath, headPath, string.Format("{0}{0}{0}{0}{0}\n", deadbeef)); } protected SelfCleaningDirectory BuildSelfCleaningDirectory() @@ -76,14 +176,88 @@ protected SelfCleaningDirectory BuildSelfCleaningDirectory(string path) return new SelfCleaningDirectory(this, path); } - protected TemporaryCloneOfTestRepo BuildTemporaryCloneOfTestRepo() + protected string SandboxBareTestRepo() + { + return Sandbox(BareTestRepoPath); + } + + protected string SandboxStandardTestRepo() + { + return Sandbox(StandardTestRepoWorkingDirPath); + } + + protected string SandboxMergedTestRepo() + { + return Sandbox(MergedTestRepoWorkingDirPath); + } + + protected string SandboxStandardTestRepoGitDir() + { + return Sandbox(Path.Combine(StandardTestRepoWorkingDirPath)); + } + + protected string SandboxMergeTestRepo() + { + 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; + } + + public string SandboxWorktreeTestRepo() + { + return Sandbox(WorktreeTestRepoWorkingDirPath, WorktreeTestRepoWorktreesDirPath); + } + + protected string SandboxPackBuilderTestRepo() + { + return Sandbox(PackBuilderTestRepoPath); + } + + protected string Sandbox(string sourceDirectoryPath, params string[] additionalSourcePaths) { - return BuildTemporaryCloneOfTestRepo(BareTestRepoPath); + var scd = BuildSelfCleaningDirectory(); + var source = new DirectoryInfo(sourceDirectoryPath); + + var clonePath = Path.Combine(scd.DirectoryPath, source.Name); + DirectoryHelper.CopyFilesRecursively(source, new DirectoryInfo(clonePath)); + + foreach (var additionalPath in additionalSourcePaths) + { + var additional = new DirectoryInfo(additionalPath); + var targetForAdditional = Path.Combine(scd.DirectoryPath, additional.Name); + + DirectoryHelper.CopyFilesRecursively(additional, new DirectoryInfo(targetForAdditional)); + } + + return clonePath; } - protected TemporaryCloneOfTestRepo BuildTemporaryCloneOfTestRepo(string path) + protected string InitNewRepository(bool isBare = false) { - return new TemporaryCloneOfTestRepo(this, path); + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + return Repository.Init(scd.DirectoryPath, isBare); } public void Register(string directoryPath) @@ -91,19 +265,27 @@ public void Register(string directoryPath) directories.Add(directoryPath); } - public void Dispose() + 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 void InconclusiveIf(Func predicate, string message) + protected static void InconclusiveIf(Func predicate, string message) { if (!predicate()) { @@ -113,6 +295,42 @@ protected void InconclusiveIf(Func predicate, string message) throw new SkipException(message); } + protected void RequiresDotNetOrMonoGreaterThanOrEqualTo(System.Version minimumVersion) + { + Type type = Type.GetType("Mono.Runtime"); + + if (type == null) + { + // We're running on top of .Net + return; + } + + MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); + + if (displayName == null) + { + throw new InvalidOperationException("Cannot access Mono.RunTime.GetDisplayName() method."); + } + + var version = (string)displayName.Invoke(null, null); + + System.Version current; + + try + { + current = new System.Version(version.Split(' ')[0]); + } + catch (Exception e) + { + throw new Exception(string.Format("Cannot parse Mono version '{0}'.", version), e); + } + + InconclusiveIf(() => current < minimumVersion, + string.Format( + "Current Mono version is {0}. Minimum required version to run this test is {1}.", + current, minimumVersion)); + } + protected static void AssertValueInConfigFile(string configFilePath, string regex) { var text = File.ReadAllText(configFilePath); @@ -120,45 +338,187 @@ protected static void AssertValueInConfigFile(string configFilePath, string rege Assert.True(r.Success, text); } - public RepositoryOptions BuildFakeConfigs(SelfCleaningDirectory scd) + private static void BuildFakeRepositoryOptions(SelfCleaningDirectory scd, out string global, out string xdg, out string system, out string programData) { - var options = BuildFakeRepositoryOptions(scd); + string confs = Path.Combine(scd.DirectoryPath, "confs"); + Directory.CreateDirectory(confs); - 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()); + 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); + } - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = system{0}", Environment.NewLine); - File.WriteAllText(options.SystemConfigurationLocation, sb.ToString()); + /// + /// 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 identity to use for user.name and user.email + /// The path to the configuration file + protected void CreateConfigurationWithDummyUser(Repository repo, Identity identity) + { + CreateConfigurationWithDummyUser(repo, identity.Name, identity.Email); + } - sb = new StringBuilder() - .AppendFormat("[Woot]{0}", Environment.NewLine) - .AppendFormat("this-rocks = xdg{0}", Environment.NewLine); - File.WriteAllText(options.XdgConfigurationLocation, sb.ToString()); + protected void CreateConfigurationWithDummyUser(Repository repo, string name, string email) + { + Configuration config = repo.Config; + { + if (name != null) + { + config.Set("user.name", name); + } - return options; + if (email != null) + { + config.Set("user.email", email); + } + } } - private static RepositoryOptions BuildFakeRepositoryOptions(SelfCleaningDirectory scd) + /// + /// Asserts that the commit has been authored and committed by the specified signature + /// + /// The commit + /// The identity to compare author and commiter to + protected void AssertCommitIdentitiesAre(Commit commit, Identity identity) { - string confs = Path.Combine(scd.DirectoryPath, "confs"); - Directory.CreateDirectory(confs); + 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) + { + string filePath = Path.Combine(parent, file); + 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; + } + + protected static string Touch(string parent, string file, Stream stream) + { + Debug.Assert(stream != null); + + string filePath = Path.Combine(parent, file); + 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)) + { + CopyStream(stream, fs); + 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)); + } + + protected string Expected(string filenameFormat, params object[] args) + { + return Expected(string.Format(CultureInfo.InvariantCulture, filenameFormat, args)); + } + + protected static void AssertRefLogEntry(IRepository repo, string canonicalName, + string message, ObjectId @from, ObjectId to, + Identity committer, DateTimeOffset before) + { + var reflogEntry = repo.Refs.Log(canonicalName).First(); + + Assert.Equal(to, reflogEntry.To); + Assert.Equal(message, reflogEntry.Message); + Assert.Equal(@from ?? ObjectId.Zero, reflogEntry.From); + + 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) + { + repository.Config.Set("core.logAllRefUpdates", enable); + } + + public static void CopyStream(Stream input, Stream output) + { + // Reused from the following Stack Overflow post with permission + // of Jon Skeet (obtained on 25 Feb 2013) + // http://stackoverflow.com/questions/411592/how-do-i-save-a-stream-to-a-file/411605#411605 + var buffer = new byte[8 * 1024]; + int len; + while ((len = input.Read(buffer, 0, buffer.Length)) > 0) + { + output.Write(buffer, 0, len); + } + } - string globalLocation = Path.Combine(confs, "my-global-config"); - string xdgLocation = Path.Combine(confs, "my-xdg-config"); - string systemLocation = Path.Combine(confs, "my-system-config"); + public static bool StreamEquals(Stream one, Stream two) + { + int onebyte, twobyte; - return new RepositoryOptions + while ((onebyte = one.ReadByte()) >= 0 && (twobyte = two.ReadByte()) >= 0) { - GlobalConfigurationLocation = globalLocation, - XdgConfigurationLocation = xdgLocation, - SystemConfigurationLocation = systemLocation, - }; + if (onebyte != twobyte) + return false; + } + + return true; + } + + public void AssertBelongsToARepository(IRepository repo, T instance) + where T : IBelongToARepository + { + 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 212c29e10..d8c14dbca 100644 --- a/LibGit2Sharp.Tests/TestHelpers/Constants.cs +++ b/LibGit2Sharp.Tests/TestHelpers/Constants.cs @@ -1,16 +1,100 @@ 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 + // authentication. Define PrivateRepoCredentials to return an instance of + // UsernamePasswordCredentials (for HTTP Basic authentication) or + // DefaultCredentials (for NTLM/Negotiate authentication). + // + // For example: + // public const string PrivateRepoUrl = "https://github.com/username/PrivateRepo"; + // ... 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(); - // Populate these to turn on live credential tests public const string PrivateRepoUrl = ""; - public const string PrivateRepoUsername = ""; - public const string PrivateRepoPassword = ""; + + 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 c85312592..636d4f198 100644 --- a/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs +++ b/LibGit2Sharp.Tests/TestHelpers/DirectoryHelper.cs @@ -1,32 +1,40 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading; namespace LibGit2Sharp.Tests.TestHelpers { public static class DirectoryHelper { + private static readonly Dictionary toRename = new Dictionary + { + { "dot_git", ".git" }, + { "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 foreach (DirectoryInfo dir in source.GetDirectories()) { - CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name)); + CopyFilesRecursively(dir, target.CreateSubdirectory(Rename(dir.Name))); } foreach (FileInfo file in source.GetFiles()) { - file.CopyTo(Path.Combine(target.FullName, file.Name)); + file.CopyTo(Path.Combine(target.FullName, Rename(file.Name))); } } - public static void DeleteSubdirectories(string parentPath) + private static string Rename(string name) { - string[] dirs = Directory.GetDirectories(parentPath); - foreach (string dir in dirs) - { - DeleteDirectory(dir); - } + return toRename.ContainsKey(name) ? toRename[name] : name; } public static void DeleteDirectory(string directoryPath) @@ -35,40 +43,61 @@ public static void DeleteDirectory(string directoryPath) 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/ExpectedFetchState.cs b/LibGit2Sharp.Tests/TestHelpers/ExpectedFetchState.cs index 512f401d7..d02b5913a 100644 --- a/LibGit2Sharp.Tests/TestHelpers/ExpectedFetchState.cs +++ b/LibGit2Sharp.Tests/TestHelpers/ExpectedFetchState.cs @@ -5,38 +5,38 @@ namespace LibGit2Sharp.Tests.TestHelpers { /// - /// Class to verify the expected state after fetching github.com/nulltoken/TestGitRepository into an empty repository. - /// Includes the expected reference callbacks and the expected branches / tags after fetch is completed. + /// Class to verify the expected state after fetching github.com/nulltoken/TestGitRepository into an empty repository. + /// Includes the expected reference callbacks and the expected branches / tags after fetch is completed. /// internal class ExpectedFetchState { /// - /// Name of the Remote being fetched from. + /// Name of the Remote being fetched from. /// internal string RemoteName { get; private set; } /// - /// Expected branch tips after fetching into an empty repository. + /// Expected branch tips after fetching into an empty repository. /// private Dictionary ExpectedBranchTips = new Dictionary(); /// - /// Expected tags after fetching into an empty repository + /// Expected tags after fetching into an empty repository /// private Dictionary ExpectedTags = new Dictionary(); /// - /// References that we expect to be updated in the UpdateReferenceTips callback. + /// References that we expect to be updated in the UpdateReferenceTips callback. /// private Dictionary ExpectedReferenceUpdates = new Dictionary(); /// - /// References that were actually updated in the UpdateReferenceTips callback. + /// References that were actually updated in the UpdateReferenceTips callback. /// private Dictionary ObservedReferenceUpdates = new Dictionary(); /// - /// Constructor. + /// Constructor. /// /// Name of the remote being updated. public ExpectedFetchState(string remoteName) @@ -45,7 +45,7 @@ public ExpectedFetchState(string remoteName) } /// - /// Add information on a branch that is expected to be updated during a fetch. + /// Add information on a branch that is expected to be updated during a fetch. /// /// Name of the branch. /// Old ID of the branch reference. @@ -58,7 +58,7 @@ public void AddExpectedBranch(string branchName, ObjectId oldId, ObjectId newId) } /// - /// Add information on a tag that is expected to be updated during a fetch. + /// Add information on a tag that is expected to be updated during a fetch. /// /// Name of the tag. /// Old ID of the tag. @@ -73,13 +73,13 @@ public void AddExpectedTag(string tagName, ObjectId oldId, TestRemoteInfo.Expect } /// - /// Handler to hook up to UpdateTips callback. + /// Handler to hook up to UpdateTips callback. /// /// Name of reference being updated. /// Old ID of reference. /// New ID of reference. - /// - public int RemoteUpdateTipsHandler(string referenceName, ObjectId oldId, ObjectId newId) + /// True to continue, false to cancel. + public bool RemoteUpdateTipsHandler(string referenceName, ObjectId oldId, ObjectId newId) { // assert that we have not seen this reference before Assert.DoesNotContain(referenceName, ObservedReferenceUpdates.Keys); @@ -97,14 +97,14 @@ public int RemoteUpdateTipsHandler(string referenceName, ObjectId oldId, ObjectI Assert.Equal(referenceUpdate.NewId, newId); } - return 0; + return true; } /// - /// Check that all expected references have been updated. + /// Check that all expected references have been updated. /// /// Repository object whose state will be checked against expected state. - public void CheckUpdatedReferences(Repository repo) + public void CheckUpdatedReferences(IRepository repo) { // Verify the expected branches. // First, verify the expected branches have been created and @@ -157,17 +157,17 @@ public void CheckUpdatedReferences(Repository repo) #region ExpectedFetchState /// - /// Structure to track a reference that has been updated. + /// Structure to track a reference that has been updated. /// private struct ReferenceUpdate { /// - /// Old ID of the reference. + /// Old ID of the reference. /// public ObjectId OldId; /// - /// New ID of the reference. + /// New ID of the reference. /// public ObjectId NewId; 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/OdbHelper.cs b/LibGit2Sharp.Tests/TestHelpers/OdbHelper.cs new file mode 100644 index 000000000..b1ae071a9 --- /dev/null +++ b/LibGit2Sharp.Tests/TestHelpers/OdbHelper.cs @@ -0,0 +1,16 @@ +using System.IO; +using System.Text; + +namespace LibGit2Sharp.Tests.TestHelpers +{ + public static class OdbHelper + { + public static Blob CreateBlob(IRepository repo, string content) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(content))) + { + return repo.ObjectDatabase.CreateBlob(stream); + } + } + } +} 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 fbf734fb5..000000000 --- a/LibGit2Sharp.Tests/TestHelpers/SkippableFactAttribute.cs +++ /dev/null @@ -1,101 +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 Xunit; -using Xunit.Sdk; - -namespace LibGit2Sharp.Tests.TestHelpers -{ - class SkippableFactAttribute : FactAttribute - { - protected override IEnumerable EnumerateTestCommands(IMethodInfo method) - { - yield return new SkippableTestCommand(method); - } - - class SkippableTestCommand : FactCommand - { - public SkippableTestCommand(IMethodInfo method) : base(method) { } - - public override MethodResult Execute(object testClass) - { - try - { - return base.Execute(testClass); - } - catch (SkipException e) - { - return new SkipResult(testMethod, DisplayName, e.Reason); - } - } - } - } - - class SkipException : Exception - { - public SkipException(string reason) - { - Reason = reason; - } - - public string Reason { get; 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/TemporaryCloneOfTestRepo.cs b/LibGit2Sharp.Tests/TestHelpers/TemporaryCloneOfTestRepo.cs deleted file mode 100644 index 72d319d81..000000000 --- a/LibGit2Sharp.Tests/TestHelpers/TemporaryCloneOfTestRepo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.IO; - -namespace LibGit2Sharp.Tests.TestHelpers -{ - public class TemporaryCloneOfTestRepo : SelfCleaningDirectory - { - public TemporaryCloneOfTestRepo(IPostTestDirectoryRemover directoryRemover, string sourceDirectoryPath) - : base(directoryRemover) - { - var source = new DirectoryInfo(sourceDirectoryPath); - - if (Directory.Exists(Path.Combine(sourceDirectoryPath, ".git"))) - { - // If there is a .git subfolder, we're dealing with a non-bare repo and we have to - // copy the working folder as well - - RepositoryPath = Path.Combine(DirectoryPath, ".git"); - - DirectoryHelper.CopyFilesRecursively(source, new DirectoryInfo(DirectoryPath)); - } - else - { - // It's a bare repo - - var tempRepository = new DirectoryInfo(Path.Combine(DirectoryPath, source.Name)); - - RepositoryPath = tempRepository.FullName; - - DirectoryHelper.CopyFilesRecursively(source, tempRepository); - } - } - - public string RepositoryPath { get; private set; } - } -} diff --git a/LibGit2Sharp.Tests/TestHelpers/TestRemoteInfo.cs b/LibGit2Sharp.Tests/TestHelpers/TestRemoteInfo.cs index 65ad922f7..7703bea5f 100644 --- a/LibGit2Sharp.Tests/TestHelpers/TestRemoteInfo.cs +++ b/LibGit2Sharp.Tests/TestHelpers/TestRemoteInfo.cs @@ -3,18 +3,18 @@ namespace LibGit2Sharp.Tests.TestHelpers { /// - /// This is the state of the test repository based at: - /// github.com/libgit2/TestGitRepository + /// This is the state of the test repository based at: + /// github.com/libgit2/TestGitRepository /// public class TestRemoteInfo { /// - /// Expected Branch tips of the remote repository. + /// Expected Branch tips of the remote repository. /// public Dictionary BranchTips = new Dictionary(); /// - /// Expected Tags of the remote repository. + /// Expected Tags of the remote repository. /// public Dictionary Tags = new Dictionary(); @@ -35,7 +35,7 @@ private TestRemoteInfo() #region ExpectedTagInfo class /// - /// Helper class to contain expected info of tags in the test repository. + /// Helper class to contain expected info of tags in the test repository. /// public class ExpectedTagInfo { 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 310b3212f..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); @@ -28,13 +29,14 @@ public void CanBuildATreeDefinitionFromATree() [Fact] public void BuildingATreeDefinitionWithBadParamsThrows() { - Assert.Throws(() => TreeDefinition.From(null)); + Assert.Throws(() => TreeDefinition.From(default(Tree))); } [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); @@ -63,21 +66,22 @@ public void RequestingAnEntryWithBadParamsThrows() } [Theory] - [InlineData("1/branch_file.txt", "100755", GitObjectType.Blob, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057")] - [InlineData("README", "100644", GitObjectType.Blob, "a8233120f6ad708f843d861ce2b7228ec4e3dec6")] - [InlineData("branch_file.txt", "100644", GitObjectType.Blob, "45b983be36b73c0788dc9cbcb76cbb80fc7bb057")] - [InlineData("new.txt", "100644", GitObjectType.Blob, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd")] - [InlineData("1", "040000", GitObjectType.Tree, "7f76480d939dc401415927ea7ef25c676b8ddb8f")] - public void CanRetrieveEntries(string path, string expectedAttributes, GitObjectType expectedType, string expectedSha) + [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")] + 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); TreeEntryDefinition ted = td[path]; Assert.Equal(ToMode(expectedAttributes), ted.Mode); - Assert.Equal(expectedType, ted.Type); + Assert.Equal(expectedType, ted.TargetType); Assert.Equal(new ObjectId(expectedSha), ted.TargetId); } } @@ -91,12 +95,17 @@ private static Mode ToMode(string expectedAttributes) [Theory] [InlineData("README", "README_TOO")] [InlineData("README", "1/README")] + [InlineData("README", "1/2/README")] [InlineData("1/branch_file.txt", "1/another_one.txt")] [InlineData("1/branch_file.txt", "another_one.txt")] [InlineData("1/branch_file.txt", "1/2/another_one.txt")] + [InlineData("1/branch_file.txt", "1/2/3/another_one.txt")] + [InlineData("1", "2")] + [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]); @@ -111,6 +120,126 @@ public void CanAddAnExistingTreeEntryDefinition(string sourcePath, string target } } + [Fact] + public void CanAddAnExistingGitLinkTreeEntryDefinition() + { + const string sourcePath = "sm_unchanged"; + const string targetPath = "sm_from_td"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.Null(td[targetPath]); + + TreeEntryDefinition ted = td[sourcePath]; + td.Add(targetPath, ted); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(ted, fetched); + } + } + + 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")] + [InlineData("README", "1/2/README")] + [InlineData("1/branch_file.txt", "1/another_one.txt")] + [InlineData("1/branch_file.txt", "another_one.txt")] + [InlineData("1/branch_file.txt", "1/2/another_one.txt")] + [InlineData("1/branch_file.txt", "1/2/3/another_one.txt")] + [InlineData("1", "2")] + [InlineData("1", "2/3")] + [InlineData("1", "C:\\/10")] + [InlineData("1", " : * ? \" < > |")] + [InlineData("1", StringOf600Chars)] + public void CanAddAndRemoveAnExistingTreeEntry(string sourcePath, string targetPath) + { + string path = SandboxBareTestRepo(); + using (var repo = new Repository(path)) + { + var tree = repo.Head.Tip.Tree; + var td = TreeDefinition.From(tree); + Assert.Null(td[targetPath]); + + var te = tree[sourcePath]; + td.Add(targetPath, te); + + var fetched = td[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); + } + } + + [Theory] + [InlineData("sm_from_td")] + [InlineData("1/sm_from_td")] + [InlineData("1/2/sm_from_td")] + public void CanAddAnExistingGitLinkTreeEntry(string targetPath) + { + const string sourcePath = "sm_unchanged"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var tree = repo.Head.Tip.Tree; + var td = TreeDefinition.From(tree); + Assert.Null(td[targetPath]); + + var te = tree[sourcePath]; + td.Add(targetPath, te); + + var fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(te.Target.Id, fetched.TargetId); + } + } + [Theory] [InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "README_TOO")] [InlineData("a8233120f6ad708f843d861ce2b7228ec4e3dec6", "1/README")] @@ -118,7 +247,8 @@ public void CanAddAnExistingTreeEntryDefinition(string sourcePath, string target [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]); @@ -136,13 +266,85 @@ 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"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + var submodule = repo.Submodules[submodulePath]; + Assert.NotNull(submodule); + + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.NotNull(td[submodulePath]); + + td.Remove(submodulePath); + Assert.Null(td[submodulePath]); + + td.Add(submodule); + + TreeEntryDefinition fetched = td[submodulePath]; + Assert.NotNull(fetched); + + Assert.Equal(submodule.HeadCommitId, fetched.TargetId); + Assert.Equal(TreeEntryTargetType.GitLink, fetched.TargetType); + Assert.Equal(Mode.GitLink, fetched.Mode); + } + } + [Fact] 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); @@ -167,10 +369,11 @@ 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(GitObjectType.Tree, td[targetPath].Type); + Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); var objectId = new ObjectId(blobSha); var blob = repo.Lookup(objectId); @@ -182,7 +385,7 @@ public void CanReplaceAnExistingTreeWithABlob() TreeEntryDefinition fetched = td[targetPath]; Assert.NotNull(fetched); - Assert.Equal(GitObjectType.Blob, td[targetPath].Type); + Assert.Equal(TreeEntryTargetType.Blob, td[targetPath].TargetType); Assert.Equal(objectId, fetched.TargetId); Assert.Equal(Mode.NonExecutableFile, fetched.Mode); @@ -197,11 +400,12 @@ 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]); - Assert.Equal(GitObjectType.Blob, td[targetPath].Type); + Assert.Equal(TreeEntryTargetType.Blob, td[targetPath].TargetType); var objectId = new ObjectId(treeSha); var tree = repo.Lookup(objectId); @@ -211,19 +415,127 @@ public void CanReplaceAnExistingBlobWithATree(string targetPath) TreeEntryDefinition fetched = td[targetPath]; Assert.NotNull(fetched); - Assert.Equal(GitObjectType.Tree, td[targetPath].Type); + Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); Assert.Equal(objectId, fetched.TargetId); Assert.Equal(Mode.Directory, fetched.Mode); } } + [Fact] + public void CanReplaceAnExistingTreeWithAGitLink() + { + var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; + const string targetPath = "just_a_dir"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.Equal(TreeEntryTargetType.Tree, td[targetPath].TargetType); + + Assert.NotNull(td["just_a_dir/contents"]); + + td.AddGitLink(targetPath, commitId); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(commitId, fetched.TargetId); + Assert.Equal(TreeEntryTargetType.GitLink, fetched.TargetType); + Assert.Equal(Mode.GitLink, fetched.Mode); + + Assert.Null(td["just_a_dir/contents"]); + } + } + + [Fact] + public void CanReplaceAnExistingGitLinkWithATree() + { + const string treeSha = "607d96653d4d0a4f733107f7890c2e67b55b620d"; + const string targetPath = "sm_unchanged"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.NotNull(td[targetPath]); + Assert.Equal(TreeEntryTargetType.GitLink, td[targetPath].TargetType); + Assert.Equal(Mode.GitLink, td[targetPath].Mode); + + var objectId = new ObjectId(treeSha); + var tree = repo.Lookup(objectId); + + td.Add(targetPath, tree); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(objectId, fetched.TargetId); + Assert.Equal(TreeEntryTargetType.Tree, fetched.TargetType); + Assert.Equal(Mode.Directory, fetched.Mode); + } + } + + [Fact] + public void CanReplaceAnExistingBlobWithAGitLink() + { + var commitId = (ObjectId)"480095882d281ed676fe5b863569520e54a7d5c0"; + const string targetPath = "just_a_file"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.NotNull(td[targetPath]); + Assert.Equal(TreeEntryTargetType.Blob, td[targetPath].TargetType); + + td.AddGitLink(targetPath, commitId); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(TreeEntryTargetType.GitLink, td[targetPath].TargetType); + Assert.Equal(commitId, fetched.TargetId); + Assert.Equal(Mode.GitLink, fetched.Mode); + } + } + + [Fact] + public void CanReplaceAnExistingGitLinkWithABlob() + { + const string blobSha = "42cfb95cd01bf9225b659b5ee3edcc78e8eeb478"; + const string targetPath = "sm_unchanged"; + + var path = SandboxSubmoduleTestRepo(); + using (var repo = new Repository(path)) + { + TreeDefinition td = TreeDefinition.From(repo.Head.Tip.Tree); + Assert.NotNull(td[targetPath]); + Assert.Equal(TreeEntryTargetType.GitLink, td[targetPath].TargetType); + Assert.Equal(Mode.GitLink, td[targetPath].Mode); + + var objectId = new ObjectId(blobSha); + var blob = repo.Lookup(objectId); + + td.Add(targetPath, blob, Mode.NonExecutableFile); + + TreeEntryDefinition fetched = td[targetPath]; + Assert.NotNull(fetched); + + Assert.Equal(objectId, fetched.TargetId); + Assert.Equal(TreeEntryTargetType.Blob, fetched.TargetType); + Assert.Equal(Mode.NonExecutableFile, fetched.Mode); + } + } + [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(GitObjectType.Tree, td["1"].Type); + Assert.Equal(TreeEntryTargetType.Tree, td["1"].TargetType); td.Add("new/one", repo.Lookup("a823312"), Mode.NonExecutableFile) .Add("new/two", repo.Lookup("a71586c"), Mode.NonExecutableFile) @@ -239,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); @@ -257,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"]; @@ -273,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 766dcf6eb..a3a8d89eb 100644 --- a/LibGit2Sharp.Tests/TreeFixture.cs +++ b/LibGit2Sharp.Tests/TreeFixture.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; @@ -12,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); @@ -25,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; @@ -38,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; @@ -51,29 +58,47 @@ 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.Equal(3, tree.Blobs.Count()); + Assert.False(tree.IsMissing); + + IEnumerable blobs = tree + .Where(e => e.TargetType == TreeEntryTargetType.Blob) + .Select(e => e.Target) + .Cast(); + + Assert.Equal(3, blobs.Count()); } } [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.Equal(1, tree.Trees.Count()); + Assert.False(tree.IsMissing); + + IEnumerable subTrees = tree + .Where(e => e.TargetType == TreeEntryTargetType.Tree) + .Select(e => e.Target) + .Cast(); + + 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()); @@ -83,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); @@ -95,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); } @@ -106,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); } } @@ -116,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); } } @@ -126,43 +159,63 @@ 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); - // A tree entry is now fetched through a relative path to the + // A tree entry is now fetched through a relative path to the // 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); @@ -187,5 +240,55 @@ public void CanRetrieveTreeEntryPath() Assert.NotSame(anotherInstance, anInstance); } } + + [Fact] + public void CanParseSymlinkTreeEntries() + { + var path = SandboxBareTestRepo(); + + using (var repo = new Repository(path)) + { + Blob linkContent = OdbHelper.CreateBlob(repo, "1/branch_file.txt"); + + var td = TreeDefinition.From(repo.Head.Tip) + .Add("A symlink", linkContent, Mode.SymbolicLink); + + Tree t = repo.ObjectDatabase.CreateTree(td); + Assert.False(t.IsMissing); + + var te = t["A symlink"]; + + Assert.NotNull(te); + + Assert.Equal(Mode.SymbolicLink, te.Mode); + 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/TupleFixture.cs b/LibGit2Sharp.Tests/TupleFixture.cs deleted file mode 100644 index c6258648b..000000000 --- a/LibGit2Sharp.Tests/TupleFixture.cs +++ /dev/null @@ -1,54 +0,0 @@ -using LibGit2Sharp.Core.Compat; -using Xunit; - -namespace LibGit2Sharp.Tests -{ - public class TupleFixture - { - const int integer = 2; - const string stringy = "hello"; - - private readonly Tuple sut = new Tuple(integer, stringy); - - [Fact] - public void Properties() - { - Assert.Equal(integer, sut.Item1); - Assert.Equal(stringy, sut.Item2); - } - - [Fact] - public void GetHashCodeIsTheSame() - { - var sut2 = new Tuple(integer, stringy); - - Assert.Equal(sut2.GetHashCode(), sut.GetHashCode()); - } - - [Fact] - public void GetHashCodeIsDifferent() - { - var sut2 = new Tuple(integer + 1, stringy); - - Assert.NotEqual(sut2.GetHashCode(), sut.GetHashCode()); - } - - [Fact] - public void VerifyEquals() - { - var sut2 = new Tuple(integer, stringy); - - Assert.True(sut.Equals(sut2)); - Assert.True(Equals(sut, sut2)); - } - - [Fact] - public void VerifyNotEquals() - { - var sut2 = new Tuple(integer + 1, stringy); - - Assert.False(sut.Equals(sut2)); - Assert.False(Equals(sut, sut2)); - } - } -} diff --git a/LibGit2Sharp.Tests/UnstageFixture.cs b/LibGit2Sharp.Tests/UnstageFixture.cs new file mode 100644 index 000000000..1eeee0e72 --- /dev/null +++ b/LibGit2Sharp.Tests/UnstageFixture.cs @@ -0,0 +1,314 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; +using Xunit.Extensions; + +namespace LibGit2Sharp.Tests +{ + public class UnstageFixture : BaseFixture + { + + [Fact] + public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOfHead() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + string filename = Path.Combine("1", "branch_file.txt"); + const string posixifiedFileName = "1/branch_file.txt"; + ObjectId blobId = repo.Index[posixifiedFileName].Id; + + string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); + + File.AppendAllText(fullpath, "Is there there anybody out there?"); + Commands.Stage(repo, filename); + + Assert.Equal(count, repo.Index.Count); + Assert.NotEqual((blobId), repo.Index[posixifiedFileName].Id); + + Commands.Unstage(repo, posixifiedFileName); + + Assert.Equal(count, repo.Index.Count); + Assert.Equal(blobId, repo.Index[posixifiedFileName].Id); + } + } + + [Fact] + public void CanStageAndUnstageAnIgnoredFile() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + Touch(repo.Info.WorkingDirectory, ".gitignore", "*.ign" + Environment.NewLine); + + const string relativePath = "Champa.ign"; + Touch(repo.Info.WorkingDirectory, relativePath, "On stage!" + Environment.NewLine); + + Assert.Equal(FileStatus.Ignored, repo.RetrieveStatus(relativePath)); + + Commands.Stage(repo, relativePath, new StageOptions { IncludeIgnored = true }); + Assert.Equal(FileStatus.NewInIndex, repo.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.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 = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + Assert.Equal(doesCurrentlyExistInTheIndex, (repo.Index[relativePath] != null)); + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Commands.Unstage(repo, relativePath); + + Assert.Equal(count + expectedIndexCountVariation, repo.Index.Count); + Assert.Equal(doesExistInTheIndexOnceStaged, (repo.Index[relativePath] != null)); + Assert.Equal(expectedStatusOnceStaged, repo.RetrieveStatus(relativePath)); + } + } + + + [Theory] + [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(SandboxStandardTestRepo())) + { + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); + } + } + + [Theory] + [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(SandboxStandardTestRepo())) + { + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions() { ShouldFailOnUnmatchedPath = false }); + + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + } + } + + [Fact] + public void CanUnstageTheRemovalOfAFile() + { + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + int count = repo.Index.Count; + + const string filename = "deleted_staged_file.txt"; + + string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); + Assert.False(File.Exists(fullPath)); + + Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(filename)); + + Commands.Unstage(repo, filename); + Assert.Equal(count + 1, repo.Index.Count); + + Assert.Equal(FileStatus.DeletedFromWorkdir, repo.RetrieveStatus(filename)); + } + } + + [Fact] + public void CanUnstageUntrackedFileAgainstAnOrphanedHead() + { + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + const string relativePath = "a.txt"; + Touch(repo.Info.WorkingDirectory, relativePath, "hello test file\n"); + + Commands.Stage(repo, relativePath); + + Commands.Unstage(repo, relativePath); + RepositoryStatus status = repo.RetrieveStatus(); + Assert.Empty(status.Staged); + Assert.Single(status.Untracked); + + Assert.Throws(() => Commands.Unstage(repo, "i-dont-exist", new ExplicitPathsOptions())); + } + } + + [Theory] + [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(SandboxStandardTestRepo())) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/orphaned"); + Assert.True(repo.Info.IsHeadUnborn); + + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Assert.Throws(() => Commands.Unstage(repo, relativePath, new ExplicitPathsOptions())); + } + } + + [Theory] + [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(SandboxStandardTestRepo())) + { + repo.Refs.UpdateTarget("HEAD", "refs/heads/orphaned"); + Assert.True(repo.Info.IsHeadUnborn); + + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + + Commands.Unstage(repo, relativePath); + Commands.Unstage(repo, relativePath, new ExplicitPathsOptions { ShouldFailOnUnmatchedPath = false }); + + Assert.Equal(currentStatus, repo.RetrieveStatus(relativePath)); + } + } + + [Fact] + public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string path = SandboxStandardTestRepo(); + using (var repo = new Repository(path)) + { + DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); + + const string filename = "unit_test.txt"; + string fullPath = Touch(di.FullName, filename, "some contents"); + + Assert.Throws(() => Commands.Unstage(repo, fullPath)); + } + } + + [Fact] + public void UnstagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirAgainstAnOrphanedHeadThrows() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + string repoPath = InitNewRepository(); + + using (var repo = new Repository(repoPath)) + { + DirectoryInfo di = Directory.CreateDirectory(scd.DirectoryPath); + + const string filename = "unit_test.txt"; + string fullPath = Touch(di.FullName, filename, "some contents"); + + Assert.Throws(() => Commands.Unstage(repo, fullPath)); + } + } + + [Fact] + public void UnstagingFileWithBadParamsThrows() + { + var path = SandboxStandardTestRepoGitDir(); + using (var repo = new Repository(path)) + { + 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(SandboxStandardTestRepo())) + { + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + + 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); + + Commands.Unstage(repo, new string[] { "branch_file.txt" }); + + 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(SandboxStandardTestRepo())) + { + Commands.Move(repo, "branch_file.txt", "renamed_branch_file.txt"); + + RepositoryStatus oldStatus = repo.RetrieveStatus(); + Assert.Single(oldStatus.RenamedInIndex); + Assert.Equal(FileStatus.RenamedInIndex, oldStatus["renamed_branch_file.txt"].State); + + Commands.Unstage(repo, new string[] { "renamed_branch_file.txt" }); + + 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(SandboxStandardTestRepo())) + { + 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.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/desktop/ShadowCopyFixture.cs b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs new file mode 100644 index 000000000..d9618c06c --- /dev/null +++ b/LibGit2Sharp.Tests/desktop/ShadowCopyFixture.cs @@ -0,0 +1,111 @@ +using System; +using System.IO; +using System.Reflection; +using System.Security; +using System.Security.Permissions; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class ShadowCopyFixture : BaseFixture + { + [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(Constants.TemporaryReposPath, Path.GetRandomFileName()); + Directory.CreateDirectory(cachePath); + + var setup = new AppDomainSetup + { + ApplicationBase = Path.GetDirectoryName(new Uri(assembly.CodeBase).LocalPath), + ApplicationName = "ShadowWalker", + ShadowCopyFiles = "true", + CachePath = cachePath + }; + + setup.ShadowCopyDirectories = setup.ApplicationBase; + + AppDomain domain = AppDomain.CreateDomain( + setup.ApplicationName, + null, + setup, new PermissionSet(PermissionState.Unrestricted)); + + // Instantiate from the remote domain + var wrapper = (Wrapper)domain.CreateInstanceAndUnwrap(assembly.FullName, type.FullName); + + // Ensure that LibGit2Sharp correctly probes for the native binaries + // from the other domain + string repoPath = BuildSelfCleaningDirectory().DirectoryPath; + wrapper.CanInitANewRepositoryFromAShadowCopiedAssembly(repoPath); + + Assembly sourceAssembly = typeof(IRepository).Assembly; + + // Ensure both assemblies share the same escaped code base... + string cachedAssemblyEscapedCodeBase = wrapper.AssemblyEscapedCodeBase; + Assert.Equal(sourceAssembly.EscapedCodeBase, cachedAssemblyEscapedCodeBase); + + // ...but are currently loaded from different locations... + string cachedAssemblyLocation = wrapper.AssemblyLocation; + 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.StartsWith(cachedAssembliesPath, cachedAssemblyLocation); + + 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); + } + + public class Wrapper : MarshalByRefObject + { + private readonly Assembly assembly; + public Wrapper() + { + assembly = typeof(IRepository).Assembly; + } + + public void CanInitANewRepositoryFromAShadowCopiedAssembly(string path) + { + var gitDirPath = Repository.Init(path); + + using (var repo = new Repository(gitDirPath)) + { + Assert.NotNull(repo); + } + } + + public string AssemblyLocation + { + get + { + return assembly.Location; + } + } + + public string AssemblyEscapedCodeBase + { + get + { + return assembly.EscapedCodeBase; + } + } + } + } +} diff --git a/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs new file mode 100644 index 000000000..4e3b03ce3 --- /dev/null +++ b/LibGit2Sharp.Tests/desktop/SmartSubtransportFixture.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using LibGit2Sharp.Tests.TestHelpers; +using Xunit; + +namespace LibGit2Sharp.Tests +{ + public class SmartSubtransportFixture : BaseFixture + { + // Warning: this certification validation callback will accept *all* + // SSL certificates without validation. This is *only* for testing. + // Do *NOT* enable this in production. + private readonly RemoteCertificateValidationCallback certificateValidationCallback = + (sender, certificate, chain, errors) => { return true; }; + + [Theory] + [InlineData("http", "http://github.com/libgit2/TestGitRepository")] + [InlineData("https", "https://github.com/libgit2/TestGitRepository")] + public void CustomSmartSubtransportTest(string scheme, string url) + { + string remoteName = "testRemote"; + + var scd = BuildSelfCleaningDirectory(); + var repoPath = 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(repoPath)) + { + 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); + } + + // Add the expected tags + string[] expectedTagNames = { "blob", "commit_tree", "annotated_tag" }; + foreach (string tagName in expectedTagNames) + { + TestRemoteInfo.ExpectedTagInfo expectedTagInfo = expectedResults.Tags[tagName]; + expectedFetchState.AddExpectedTag(tagName, ObjectId.Zero, expectedTagInfo); + } + + // Perform the actual fetch + Commands.Fetch(repo, remoteName, Array.Empty(), + new FetchOptions { OnUpdateTips = expectedFetchState.RemoteUpdateTipsHandler, TagFetchMode = TagFetchMode.Auto }, + null); + + // Verify the expected + expectedFetchState.CheckUpdatedReferences(repo); + } + } + finally + { + GlobalSettings.UnregisterSmartSubtransport(registration); + + ServicePointManager.ServerCertificateValidationCallback -= certificateValidationCallback; + } + } + + //[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() + { + SmartSubtransportRegistration httpRegistration = + GlobalSettings.RegisterSmartSubtransport("http"); + + try + { + Assert.Throws(() => + GlobalSettings.RegisterSmartSubtransport("http")); + } + finally + { + GlobalSettings.UnregisterSmartSubtransport(httpRegistration); + } + } + + [Fact] + public void CannotUnregisterTwice() + { + SmartSubtransportRegistration httpRegistration = + GlobalSettings.RegisterSmartSubtransport("http"); + + GlobalSettings.UnregisterSmartSubtransport(httpRegistration); + + Assert.Throws(() => + GlobalSettings.UnregisterSmartSubtransport(httpRegistration)); + } + + private class MockSmartSubtransport : RpcSmartSubtransport + { + protected override SmartSubtransportStream Action(string url, GitSmartSubtransportAction action) + { + string endpointUrl, contentType = null; + bool isPost = false; + + switch (action) + { + case GitSmartSubtransportAction.UploadPackList: + endpointUrl = string.Concat(url, "/info/refs?service=git-upload-pack"); + break; + + case GitSmartSubtransportAction.UploadPack: + 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"); + break; + + case GitSmartSubtransportAction.ReceivePack: + endpointUrl = string.Concat(url, "/git-receive-pack"); + contentType = "application/x-git-receive-pack-request"; + isPost = true; + break; + + default: + throw new InvalidOperationException(); + } + + return new MockSmartSubtransportStream(this, endpointUrl, isPost, contentType); + } + + private class MockSmartSubtransportStream : SmartSubtransportStream + { + private static int MAX_REDIRECTS = 5; + + private MemoryStream postBuffer = new MemoryStream(); + private Stream responseStream; + + public MockSmartSubtransportStream(MockSmartSubtransport parent, string endpointUrl, bool isPost, string contentType) + : base(parent) + { + EndpointUrl = endpointUrl; + IsPost = isPost; + ContentType = contentType; + } + + private string EndpointUrl + { + get; + set; + } + + private bool IsPost + { + get; + set; + } + + private string ContentType + { + get; + set; + } + + public override int Write(Stream dataStream, long length) + { + byte[] buffer = new byte[4096]; + long writeTotal = 0; + + while (length > 0) + { + int readLen = dataStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length)); + + if (readLen == 0) + { + break; + } + + postBuffer.Write(buffer, 0, readLen); + length -= readLen; + writeTotal += readLen; + } + + if (writeTotal < length) + { + throw new EndOfStreamException("Could not write buffer (short read)"); + } + + return 0; + } + + 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; + webRequest.AllowAutoRedirect = false; + + if (isPost) + { + webRequest.Method = "POST"; + webRequest.ContentType = contentType; + } + + return webRequest; + } + + private HttpWebResponse GetResponseWithRedirects() + { + HttpWebRequest request = CreateWebRequest(EndpointUrl, IsPost, ContentType); + HttpWebResponse response = null; + + for (int i = 0; i < MAX_REDIRECTS; i++) + { + if (IsPost && postBuffer.Length > 0) + { + postBuffer.Seek(0, SeekOrigin.Begin); + + using (Stream requestStream = request.GetRequestStream()) + { + postBuffer.WriteTo(requestStream); + } + } + + 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) + { + request = CreateWebRequest(response.Headers["Location"], IsPost, ContentType); + continue; + } + + + break; + } + + if (response == null) + { + throw new Exception("Too many redirects"); + } + + return response; + } + + public override int Read(Stream dataStream, long length, out long readTotal) + { + byte[] buffer = new byte[4096]; + readTotal = 0; + + if (responseStream == null) + { + HttpWebResponse response = GetResponseWithRedirects(); + responseStream = response.GetResponseStream(); + } + + while (length > 0) + { + int readLen = responseStream.Read(buffer, 0, (int)Math.Min(buffer.Length, length)); + + if (readLen == 0) + break; + + dataStream.Write(buffer, 0, readLen); + readTotal += readLen; + length -= readLen; + } + + return 0; + } + + protected override void Free() + { + if (responseStream != null) + { + responseStream.Dispose(); + responseStream = null; + } + + base.Free(); + } + } + } + } +} 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/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 499d327d1..b5ddd7963 100644 --- a/LibGit2Sharp/AmbiguousSpecificationException.cs +++ b/LibGit2Sharp/AmbiguousSpecificationException.cs @@ -1,48 +1,69 @@ 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. + /// 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. + /// Initializes a new instance of the class. /// public AmbiguousSpecificationException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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)) { } /// - /// 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. + /// 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 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 AmbiguousSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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/ArchiverBase.cs b/LibGit2Sharp/ArchiverBase.cs new file mode 100644 index 000000000..8268c1075 --- /dev/null +++ b/LibGit2Sharp/ArchiverBase.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace LibGit2Sharp +{ + /// + /// The archiving method needs to be passed an inheritor of this class, which will then be used + /// to provide low-level archiving facilities (tar, zip, ...). + /// + /// + /// + /// + public abstract class ArchiverBase + { + /// + /// Override this method to perform operations before the archiving of each entry of the tree takes place. + /// + /// The tree that will be archived + /// The ObjectId of the commit being archived, or null if there is no commit. + /// The modification time that will be used for the files in the archive. + public virtual void BeforeArchiving(Tree tree, ObjectId oid, DateTimeOffset modificationTime) + { } + + /// + /// Override this method to perform operations after the archiving of each entry of the tree took place. + /// + /// The tree that was archived + /// The ObjectId of the commit being archived, or null if there is no commit. + /// The modification time that was used for the files in the archive. + public virtual void AfterArchiving(Tree tree, ObjectId oid, DateTimeOffset modificationTime) + { } + + internal void OrchestrateArchiving(Tree tree, ObjectId oid, DateTimeOffset modificationTime) + { + BeforeArchiving(tree, oid, modificationTime); + + ArchiveTree(tree, "", modificationTime); + + AfterArchiving(tree, oid, modificationTime); + } + + private void ArchiveTree(IEnumerable tree, string path, DateTimeOffset modificationTime) + { + foreach (var entry in tree) + { + AddTreeEntry(Path.Combine(path, entry.Name), entry, modificationTime); + + // Recurse if we have subtrees + if (entry.Mode == Mode.Directory) + { + ArchiveTree((Tree)entry.Target, Path.Combine(path, entry.Name), modificationTime); + } + } + } + + /// + /// Implements the archiving of a TreeEntry in a given format. + /// + /// The path of the entry in the archive. + /// The entry to archive. + /// The datetime the entry was last modified. + protected abstract void AddTreeEntry(string path, TreeEntry entry, DateTimeOffset modificationTime); + } +} diff --git a/LibGit2Sharp/BareRepositoryException.cs b/LibGit2Sharp/BareRepositoryException.cs index 46a4e5e08..412e5e4d4 100644 --- a/LibGit2Sharp/BareRepositoryException.cs +++ b/LibGit2Sharp/BareRepositoryException.cs @@ -1,55 +1,73 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// The exception that is thrown when an operation which requires a - /// working directory is performed against a bare repository. + /// 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. + /// Initializes a new instance of the class. /// public BareRepositoryException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 BareRepositoryException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 new file mode 100644 index 000000000..6350a9bbc --- /dev/null +++ b/LibGit2Sharp/BlameHunk.cs @@ -0,0 +1,178 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A contiguous group of lines that have been traced to a single commit. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class BlameHunk : IEquatable + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.LineCount, + x => x.FinalStartLineNumber, + x => x.FinalSignature, + x => x.InitialStartLineNumber, + x => x.InitialSignature, + x => x.InitialCommit); + + internal unsafe BlameHunk(IRepository repository, git_blame_hunk* rawHunk) + { + 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->orig_path != null) + { + InitialPath = LaxUtf8Marshaler.FromNative(rawHunk->orig_path); + } + + LineCount = (int)rawHunk->lines_in_hunk.ToUInt32(); + + // Libgit2's line numbers are 1-based + 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->final_signature != null) + { + FinalSignature = new Signature(rawHunk->final_signature); + } + + if (rawHunk->orig_signature != null) + { + InitialSignature = new Signature(rawHunk->orig_signature); + } + } + + /// + /// For easier mocking + /// + protected BlameHunk() + { } + + /// + /// Determine if this hunk contains a given line. + /// + /// Line number to test + /// True if this hunk contains the given line. + public virtual bool ContainsLine(int line) + { + return FinalStartLineNumber <= line && line < FinalStartLineNumber + LineCount; + } + + /// + /// Number of lines in this hunk. + /// + public virtual int LineCount { get; private set; } + + /// + /// The line number where this hunk begins, as of + /// + public virtual int FinalStartLineNumber { get; private set; } + + /// + /// Signature of the most recent change to this hunk. + /// + public virtual Signature FinalSignature { get; private set; } + + /// + /// Commit which most recently changed this file. + /// + public virtual Commit FinalCommit { get { return finalCommit.Value; } } + + /// + /// Line number where this hunk begins, as of , in . + /// + public virtual int InitialStartLineNumber { get; private set; } + + /// + /// Signature of the oldest-traced change to this hunk. + /// + public virtual Signature InitialSignature { get; private set; } + + /// + /// Commit to which the oldest change to this hunk has been traced. + /// + public virtual Commit InitialCommit { get { return origCommit.Value; } } + + /// + /// Path to the file where this hunk originated, as of . + /// + public virtual string InitialPath { get; private set; } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "{0}-{1} ({2})", + FinalStartLineNumber, + FinalStartLineNumber + LineCount - 1, + FinalCommit.ToString().Substring(0, 7)); + } + } + + private readonly Lazy finalCommit; + private readonly Lazy origCommit; + + /// + /// Indicates whether the current object is equal to another object of the same type. + /// + /// + /// true if the current object is equal to the parameter; otherwise, false. + /// + /// An object to compare with this object. + public bool Equals(BlameHunk other) + { + return equalityHelper.Equals(this, other); + } + + /// + /// 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 BlameHunk); + } + + /// + /// Returns the hash code for this instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return equalityHelper.GetHashCode(); + } + + /// + /// Tests if two s are equal. + /// + /// First hunk to compare. + /// Second hunk to compare. + /// True if the two objects are equal; false otherwise. + public static bool operator ==(BlameHunk left, BlameHunk right) + { + return Equals(left, right); + } + + /// + /// Tests if two s are unequal. + /// + /// First hunk to compare. + /// Second hunk to compare. + /// True if the two objects are different; false otherwise. + public static bool operator !=(BlameHunk left, BlameHunk right) + { + return !Equals(left, right); + } + } +} diff --git a/LibGit2Sharp/BlameHunkCollection.cs b/LibGit2Sharp/BlameHunkCollection.cs new file mode 100644 index 000000000..2766ee7a6 --- /dev/null +++ b/LibGit2Sharp/BlameHunkCollection.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The result of a blame operation. + /// + public class BlameHunkCollection : IEnumerable + { + private readonly IRepository repo; + private readonly List hunks = new List(); + + /// + /// For easy mocking + /// + protected BlameHunkCollection() { } + + internal unsafe BlameHunkCollection(Repository repo, RepositoryHandle repoHandle, string path, BlameOptions options) + { + this.repo = repo; + + var rawopts = new git_blame_options + { + version = 1, + flags = options.Strategy.ToGitBlameOptionFlags(), + min_line = new UIntPtr((uint)options.MinLine), + max_line = new UIntPtr((uint)options.MaxLine), + }; + + if (options.StartingAt != null) + { + 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) + { + 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)) + { + var numHunks = NativeMethods.git_blame_get_hunk_count(blameHandle); + for (uint i = 0; i < numHunks; ++i) + { + var rawHunk = Proxy.git_blame_get_hunk_byindex(blameHandle, i); + hunks.Add(new BlameHunk(this.repo, rawHunk)); + } + } + } + + /// + /// Access blame hunks by index. + /// + /// The index of the hunk to retrieve + /// The at the given index. + public virtual BlameHunk this[int idx] + { + get { return hunks[idx]; } + } + + /// + /// Access blame hunks by the file line. + /// + /// Line number to search for + /// The that contains the specified file line. + public virtual BlameHunk HunkForLine(int line) + { + var hunk = hunks.FirstOrDefault(x => x.ContainsLine(line)); + if (hunk != null) + { + return hunk; + } + throw new ArgumentOutOfRangeException(nameof(line), "No hunk for that line"); + } + + /// + /// Returns an enumerator that iterates through a collection. + /// + /// + /// An object that can be used to iterate through the collection. + /// + /// 2 + public virtual IEnumerator GetEnumerator() + { + return hunks.GetEnumerator(); + } + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/LibGit2Sharp/BlameOptions.cs b/LibGit2Sharp/BlameOptions.cs new file mode 100644 index 000000000..c39a3f536 --- /dev/null +++ b/LibGit2Sharp/BlameOptions.cs @@ -0,0 +1,62 @@ +namespace LibGit2Sharp +{ + /// + /// Strategy used for blaming. + /// + public enum BlameStrategy + { + /// + /// Track renames of the file, but no block movement. + /// + Default, + + // Track copies within the same file. (NOT SUPPORTED IN LIBGIT2 YET) + //TrackCopiesSameFile, + + // Track movement across files within the same commit. (NOT SUPPORTED IN LIBGIT2 YET) + //TrackCopiesSameCommitMoves, + + // Track copies across files within the same commit. (NOT SUPPORTED IN LIBGIT2 YET) + //TrackCopiesSameCommitCopies, + + // Track copies across all files in all commits. (NOT SUPPORTED IN LIBGIT2 YET) + //TrackCopiesAnyCommitCopies + } + + /// + /// Optional adjustments to the behavior of blame. + /// + public sealed class BlameOptions + { + /// + /// Strategy to use to determine the blame for each line. + /// The default is . + /// + public BlameStrategy Strategy { get; set; } + + /// + /// Latest commitish to consider (the starting point). + /// If null, blame will use HEAD. + /// + public object StartingAt { get; set; } + + /// + /// Oldest commitish to consider (the stopping point). + /// If null, blame will continue until all the lines have been blamed, + /// or until a commit with no parents is reached. + /// + public object StoppingAt { get; set; } + + /// + /// First text line in the file to blame (lines start at 1). + /// If this is set to 0, the blame begins at line 1. + /// + public int MinLine { get; set; } + + /// + /// Last text line in the file to blame (lines start at 1). + /// If this is set to 0, blame ends with the last line in the file. + /// + public int MaxLine { get; set; } + } +} diff --git a/LibGit2Sharp/Blob.cs b/LibGit2Sharp/Blob.cs index ef4b31ce6..29ef8d812 100644 --- a/LibGit2Sharp/Blob.cs +++ b/LibGit2Sharp/Blob.cs @@ -1,19 +1,23 @@ using System; using System.IO; +using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Stores the binary content of a tracked file. + /// 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; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Blob() { } @@ -21,39 +25,105 @@ 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 contents of a blob + /// 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. + /// 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 array. + /// Gets the blob content in a . /// - public virtual byte[] Content + /// Throws if blob is missing + public virtual Stream GetContentStream() { - get - { - return Proxy.git_blob_rawcontent(repo.Handle, Id, Size); - } + return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size); + } + + /// + /// Gets the blob content in a as it would be + /// 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 in a . + /// 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. /// - public virtual Stream ContentStream + /// 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) { - get + using (var reader = new StreamReader(stream, encoding ?? LaxUtf8Marshaler.Encoding, encoding == null)) { - return Proxy.git_blob_rawcontent_stream(repo.Handle, Id, Size); + return reader.ReadToEnd(); } } } diff --git a/LibGit2Sharp/BlobExtensions.cs b/LibGit2Sharp/BlobExtensions.cs deleted file mode 100644 index d1a7ce9d8..000000000 --- a/LibGit2Sharp/BlobExtensions.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class BlobExtensions - { - /// - /// Gets the blob content decoded as UTF-8. - /// - /// The blob for which the content will be returned. - /// Blob content as UTF-8 - public static string ContentAsUtf8(this Blob blob) - { - Ensure.ArgumentNotNull(blob, "blob"); - - return Encoding.UTF8.GetString(blob.Content); - } - - /// - /// Gets the blob content decoded as Unicode. - /// - /// The blob for which the content will be returned. - /// Blob content as unicode. - public static string ContentAsUnicode(this Blob blob) - { - Ensure.ArgumentNotNull(blob, "blob"); - - return Encoding.Unicode.GetString(blob.Content); - } - } -} diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index ff2992617..807456688 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -1,48 +1,43 @@ using System; using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; -using LibGit2Sharp.Core.Handles; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// A branch is a special kind of reference + /// A branch is a special kind of reference /// public class Branch : ReferenceWrapper { private readonly Lazy trackedBranch; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Branch() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repo. - /// The reference. - /// The full name of the reference + /// The repo. + /// The reference. + /// 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. - /// - /// This instance will point to no commit. - /// + /// Initializes a new instance of an orphaned class. + /// + /// This instance will point to no commit. + /// /// - /// The repo. - /// The reference. + /// The repo. + /// 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) @@ -51,10 +46,10 @@ private Branch(Repository repo, Reference reference, Func can } /// - /// Gets the pointed at by the in the . + /// Gets the pointed at by the in the . /// - /// The relative path to the from the working directory. - /// null if nothing has been found, the otherwise. + /// The relative path to the from the working directory. + /// null if nothing has been found, the otherwise. public virtual TreeEntry this[string relativePath] { get @@ -69,10 +64,10 @@ public virtual TreeEntry this[string relativePath] } /// - /// Gets a value indicating whether this instance is a remote. + /// Gets a value indicating whether this instance is a remote. /// /// - /// true if this instance is remote; otherwise, false. + /// true if this instance is remote; otherwise, false. /// public virtual bool IsRemote { @@ -80,7 +75,7 @@ public virtual bool IsRemote } /// - /// Gets the remote branch which is connected to this local one, or null if there is none. + /// Gets the remote branch which is connected to this local one, or null if there is none. /// public virtual Branch TrackedBranch { @@ -88,70 +83,42 @@ public virtual Branch TrackedBranch } /// - /// Determines if this local branch is connected to a remote one. + /// Determines if this local branch is connected to a remote one. /// public virtual bool IsTracking { get { return TrackedBranch != null; } } - private bool ExistsPathToTrackedBranch() - { - if (!IsTracking) - { - return false; - } - - if (Tip == null || TrackedBranch.Tip == null) - { - return false; - } - - if (repo.Commits.FindCommonAncestor(Tip, TrackedBranch.Tip) == null) - { - return false; - } - - return true; - } - - /// - /// Gets the number of commits, starting from the , that have been performed on this local branch and aren't known from the remote one. - /// - /// This property will return null if there is no remote branch linked to this local branch, or if the remote branch and the local branch do - /// not share a common ancestor. - /// - /// - public virtual int? AheadBy - { - get { return ExistsPathToTrackedBranch() ? Proxy.git_graph_ahead_behind(repo.Handle, TrackedBranch.Tip.Id, Tip.Id).Item1 : (int?)null; } - } - /// - /// Gets the number of commits that exist in the remote branch, on top of , and aren't known from the local one. - /// - /// This property will return null if there is no remote branch linked to this local branch, or if the remote branch and the local branch do - /// not share a common ancestor. - /// + /// Gets additional information about the tracked branch. /// - public virtual int? BehindBy + public virtual BranchTrackingDetails TrackingDetails { - get { return ExistsPathToTrackedBranch() ? Proxy.git_graph_ahead_behind(repo.Handle, TrackedBranch.Tip.Id, Tip.Id).Item2 : (int?)null; } + get { return new BranchTrackingDetails(repo, this); } } /// - /// Gets a value indicating whether this instance is current branch (HEAD) in the repository. + /// Gets a value indicating whether this instance is current branch (HEAD) in the repository. /// /// - /// true if this instance is the current branch; otherwise, false. + /// true if this instance is the current branch; otherwise, false. /// 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; + } } /// - /// Gets the that this branch points to. + /// Gets the that this branch points to. /// public virtual Commit Tip { @@ -159,43 +126,71 @@ public virtual Commit Tip } /// - /// Gets the commits on this branch. (Starts walking from the References's target). + /// Gets the commits on this branch. (Starts walking from the References's target). /// public virtual ICommitLog Commits { - get { return repo.Commits.QueryBy(new Filter { Since = this }); } + get { return repo.Commits.QueryBy(new CommitFilter { IncludeReachableFrom = this }); } } /// - /// Gets the configured to fetch from and push to. + /// Gets the configured canonical name of the upstream branch. + /// + /// This is the upstream reference to which this branch will be pushed. + /// It corresponds to the "branch.branch_name.merge" property of the config file. + /// /// - public virtual Remote Remote + public virtual string UpstreamBranchCanonicalName { get { - string remoteName; - if (IsRemote) { - remoteName = RemoteNameFromRemoteTrackingBranch(); - } - else - { - remoteName = RemoteNameFromLocalBranch(); - - if (remoteName == null) + using (var remote = repo.Network.Remotes.RemoteForName(RemoteName)) { - return null; + return remote.FetchSpecTransformToSource(CanonicalName); } } - return repo.Network.Remotes[remoteName]; + return UpstreamBranchCanonicalNameFromLocalBranch(); + } + } + + /// + /// 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 name of the remote + /// containing the tracked branch. If there is no tracking information, + /// this will return null. + /// + /// + public virtual string RemoteName + { + get + { + return IsRemote + ? RemoteNameFromRemoteTrackingBranch() + : RemoteNameFromLocalBranch(); + } + } + + private string UpstreamBranchCanonicalNameFromLocalBranch() + { + ConfigurationEntry mergeRefEntry = repo.Config.Get("branch", FriendlyName, "merge"); + + if (mergeRefEntry == null) + { + return null; } + + return mergeRefEntry.Value; } private string RemoteNameFromLocalBranch() { - ConfigurationEntry remoteEntry = repo.Config.Get("branch", Name, "remote"); + ConfigurationEntry remoteEntry = repo.Config.Get("branch", FriendlyName, "remote"); if (remoteEntry == null) { @@ -216,34 +211,7 @@ private string RemoteNameFromLocalBranch() private string RemoteNameFromRemoteTrackingBranch() { - using (ReferenceSafeHandle branchPtr = repo.Refs.RetrieveReferencePtr(CanonicalName)) - { - return Proxy.git_branch_remote_name(repo.Handle, branchPtr); - } - } - - /// - /// 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 a callback for progress reporting. 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. - /// - /// Options controlling checkout behavior. - /// Callback method to report checkout progress updates through. - public virtual void Checkout(CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress) - { - repo.Checkout(this, checkoutOptions, onCheckoutProgress); + return Proxy.git_branch_remote_name(repo.Handle, CanonicalName, false); } private Branch ResolveTrackedBranch() @@ -253,7 +221,7 @@ private Branch ResolveTrackedBranch() return null; } - string trackedReferenceName = Proxy.git_branch_tracking_name(repo.Handle, CanonicalName); + string trackedReferenceName = Proxy.git_branch_upstream_name(repo.Handle, CanonicalName); if (trackedReferenceName == null) { @@ -267,34 +235,34 @@ private Branch ResolveTrackedBranch() return branch; } - return new Branch(repo, new VoidReference(trackedReferenceName), trackedReferenceName); + return new Branch(repo, new VoidReference(repo, trackedReferenceName), trackedReferenceName); } private static bool IsRemoteBranch(string canonicalName) { - return canonicalName.StartsWith("refs/remotes/", StringComparison.Ordinal); + return canonicalName.LooksLikeRemoteTrackingBranch(); } /// - /// Removes redundent leading namespaces (regarding the kind of - /// reference being wrapped) from the canonical name. + /// Removes redundent leading namespaces (regarding the kind of + /// reference being wrapped) from the canonical name. /// /// The friendly shortened name protected override string Shorten() { - if (CanonicalName.StartsWith("refs/heads/", StringComparison.Ordinal)) + if (CanonicalName.LooksLikeLocalBranch()) { - return CanonicalName.Substring("refs/heads/".Length); + return CanonicalName.Substring(Reference.LocalBranchPrefix.Length); } - if (CanonicalName.StartsWith("refs/remotes/", StringComparison.Ordinal)) + if (CanonicalName.LooksLikeRemoteTrackingBranch()) { - return CanonicalName.Substring("refs/remotes/".Length); + 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 462260635..d81a48177 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -10,7 +10,7 @@ namespace LibGit2Sharp { /// - /// The collection of Branches in a + /// The collection of Branches in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class BranchCollection : IEnumerable @@ -18,22 +18,22 @@ public class BranchCollection : IEnumerable internal readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected BranchCollection() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repo. + /// The repo. internal BranchCollection(Repository repo) { this.repo = repo; } /// - /// Gets the with the specified name. + /// Gets the with the specified name. /// public virtual Branch this[string name] { @@ -64,12 +64,12 @@ public virtual Branch this[string name] private static string ShortToLocalName(string name) { - return string.Format(CultureInfo.InvariantCulture, "{0}{1}", "refs/heads/", name); + return string.Format(CultureInfo.InvariantCulture, "{0}{1}", Reference.LocalBranchPrefix, name); } private static string ShortToRemoteName(string name) { - return string.Format(CultureInfo.InvariantCulture, "{0}{1}", "refs/remotes/", name); + return string.Format(CultureInfo.InvariantCulture, "{0}{1}", Reference.RemoteTrackingBranchPrefix, name); } private static string ShortToRefName(string name) @@ -86,21 +86,20 @@ private Branch BuildFromReferenceName(string canonicalName) #region IEnumerable Members /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return Proxy.git_branch_foreach(repo.Handle, GitBranchType.GIT_BRANCH_LOCAL | GitBranchType.GIT_BRANCH_REMOTE, branchToCanoncialName) - .Select(n => this[n]) - .OrderBy(b => b.CanonicalName, StringComparer.Ordinal) - .GetEnumerator(); + return Proxy.git_branch_iterator(repo, GitBranchType.GIT_BRANCH_ALL) + .ToList() + .GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -109,89 +108,177 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Create a new local branch with the specified name + /// Create a new local branch with the specified name /// - /// The name of the branch. - /// The target commit. - /// True to allow silent overwriting a potentially existing branch, false otherwise. + /// The name of the branch. + /// Revparse spec for the target commit. /// A new . - public virtual Branch Add(string name, Commit commit, bool allowOverwrite = false) + 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. + /// True to allow silent overwriting a potentially existing branch, false otherwise. + /// A new . + public virtual Branch Add(string name, Commit commit, bool allowOverwrite) { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(commit, "commit"); - using (Proxy.git_branch_create(repo.Handle, name, commit.Id, allowOverwrite)) {} + 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_from_annotated(repo.Handle, name, committish, allowOverwrite)) + { } - return this[ShortToLocalName(name)]; + var branch = this[ShortToLocalName(name)]; + return branch; } /// - /// Create a new local branch with the specified name + /// Deletes the 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. - /// - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Branch Create(string name, string committish, bool allowOverwrite = false) + /// The name of the branch to delete. + public virtual void Remove(string name) { - return this.Add(name, committish, allowOverwrite); + Remove(name, false); } /// - /// Deletes the specified branch. + /// 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. /// - /// The branch to delete. + /// The branch to delete. 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); } } /// - /// Deletes the branch with the specified name. + /// Rename an existing local branch, using the default reflog message + /// + /// 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. + /// True to allow silent overwriting a potentially existing branch, false otherwise. + /// A new . + public virtual Branch Rename(string currentName, string newName, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); + Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); + + Branch branch = this[currentName]; + + if (branch == null) + { + throw new LibGit2SharpException("No branch named '{0}' exists in the repository."); + } + + return Rename(branch, newName, allowOverwrite); + } + + /// + /// Rename an existing local branch /// - /// The name of the branch to delete. - /// True if the provided is the name of a remote branch, false otherwise. - [Obsolete("This method will be removed in the next release. Please use Remove() instead.")] - public virtual void Delete(string name, bool isRemote = false) + /// The current local branch. + /// The new name the existing branch should bear. + /// A new . + public virtual Branch Rename(Branch branch, string newName) { - this.Remove(name, isRemote); + return Rename(branch, newName, false); } /// - /// Renames an existing local branch with a new name. + /// 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. + /// 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 Move(Branch branch, string newName, bool allowOverwrite = false) + public virtual Branch Rename(Branch branch, string newName, bool allowOverwrite) { Ensure.ArgumentNotNull(branch, "branch"); 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)); + throw new LibGit2SharpException("Cannot rename branch '{0}'. It's a remote tracking branch.", + branch.FriendlyName); } - using (ReferenceSafeHandle referencePtr = repo.Refs.RetrieveReferencePtr("refs/heads/" + branch.Name)) + using (ReferenceHandle referencePtr = repo.Refs.RetrieveReferencePtr(Reference.LocalBranchPrefix + branch.FriendlyName)) { - Proxy.git_branch_move(referencePtr, newName, allowOverwrite); + using (Proxy.git_branch_move(referencePtr, newName, allowOverwrite)) + { } } - return this[newName]; + var newBranch = this[newName]; + return newBranch; } /// - /// Update properties of a branch. + /// Update properties of a branch. /// /// The branch to update. /// Delegate to perform updates on the branch. @@ -205,38 +292,19 @@ 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) { return referenceName == "HEAD" || - referenceName.StartsWith("refs/heads/", StringComparison.Ordinal) || - referenceName.StartsWith("refs/remotes/", StringComparison.Ordinal); - } - - private static string branchToCanoncialName(IntPtr namePtr, GitBranchType branchType) - { - string shortName = Utf8Marshaler.FromNative(namePtr); - - switch (branchType) - { - case GitBranchType.GIT_BRANCH_LOCAL: - return ShortToLocalName(shortName); - case GitBranchType.GIT_BRANCH_REMOTE: - return ShortToRemoteName(shortName); - default: - return shortName; - } + referenceName.LooksLikeLocalBranch() || + referenceName.LooksLikeRemoteTrackingBranch(); } 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 0ab2538b8..000000000 --- a/LibGit2Sharp/BranchCollectionExtensions.cs +++ /dev/null @@ -1,73 +0,0 @@ -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class BranchCollectionExtensions - { - /// - /// 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. - /// The being worked with. - /// A new . - public static Branch Add(this BranchCollection branches, string name, string committish, bool allowOverwrite = false) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); - - Commit commit = branches.repo.LookupCommit(committish); - - return branches.Add(name, commit, 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 ? "refs/remotes/" + name : name; - - Branch branch = branches[branchName]; - - if (branch == null) - { - return; - } - - branches.Remove(branch); - } - - /// - /// Renames an existing local branch with a new name. - /// - /// 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 Move(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.Move(branch, newName, allowOverwrite); - } - } -} diff --git a/LibGit2Sharp/BranchTrackingDetails.cs b/LibGit2Sharp/BranchTrackingDetails.cs new file mode 100644 index 000000000..e3d99bd45 --- /dev/null +++ b/LibGit2Sharp/BranchTrackingDetails.cs @@ -0,0 +1,63 @@ +namespace LibGit2Sharp +{ + /// + /// Tracking information for a + /// + public class BranchTrackingDetails + { + private readonly HistoryDivergence historyDivergence; + + /// + /// Needed for mocking purposes. + /// + protected BranchTrackingDetails() + { } + + internal BranchTrackingDetails(Repository repo, Branch branch) + { + if (!branch.IsTracking || branch.Tip == null || branch.TrackedBranch.Tip == null) + { + historyDivergence = new NullHistoryDivergence(); + return; + } + + historyDivergence = repo.ObjectDatabase.CalculateHistoryDivergence(branch.Tip, branch.TrackedBranch.Tip); + } + + /// + /// Gets the number of commits that exist in this local branch but don't exist in the tracked one. + /// + /// This property will return null if this local branch has no upstream configuration + /// or if the upstream branch does not exist + /// + /// + public virtual int? AheadBy + { + get { return historyDivergence.AheadBy; } + } + + /// + /// Gets the number of commits that exist in the tracked branch but don't exist in this local one. + /// + /// This property will return null if this local branch has no upstream configuration + /// or if the upstream branch does not exist + /// + /// + public virtual int? BehindBy + { + get { return historyDivergence.BehindBy; } + } + + /// + /// Gets the common ancestor of the local branch and its tracked remote branch. + /// + /// This property will return null if this local branch has no upstream configuration, + /// the upstream branch does not exist, or either branch is an orphan. + /// + /// + public virtual Commit CommonAncestor + { + get { return historyDivergence.CommonAncestor; } + } + } +} diff --git a/LibGit2Sharp/BranchUpdater.cs b/LibGit2Sharp/BranchUpdater.cs index 3eb034582..b0908f272 100644 --- a/LibGit2Sharp/BranchUpdater.cs +++ b/LibGit2Sharp/BranchUpdater.cs @@ -1,12 +1,11 @@ using System; -using LibGit2Sharp.Core; using System.Globalization; -using LibGit2Sharp.Core.Handles; +using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Exposes properties of a branch that can be updated. + /// Exposes properties of a branch that can be updated. /// public class BranchUpdater { @@ -14,7 +13,7 @@ public class BranchUpdater private readonly Branch branch; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected BranchUpdater() { } @@ -29,12 +28,19 @@ internal BranchUpdater(Repository repo, Branch branch) } /// - /// Sets the upstream information for the branch. - /// - /// Passing null or string.Empty will unset the upstream. - /// + /// Sets the upstream information for the branch. + /// + /// Passing null or string.Empty will unset the upstream. + /// + /// + /// The upstream branch name is with respect to the current repository. + /// So, passing "refs/remotes/origin/master" will set the current branch + /// to track "refs/heads/master" on the origin. Passing in + /// "refs/heads/master" will result in the branch tracking the local + /// master branch. + /// /// - public virtual string Upstream + public virtual string TrackedBranch { set { @@ -48,17 +54,56 @@ public virtual string Upstream } } + /// + /// Set the upstream branch for this branch. + /// + /// To track the "master" branch on the "origin" remote, set the + /// property to "origin" and the + /// property to "refs/heads/master". + /// + /// + public virtual string UpstreamBranch + { + set + { + SetUpstreamBranch(value); + } + } + + /// + /// Set the upstream remote for this branch. + /// + /// To track the "master" branch on the "origin" remote, set the + /// property to "origin" and the + /// property to "refs/heads/master". + /// + /// + public virtual string Remote + { + set + { + SetUpstreamRemote(value); + } + } + private void UnsetUpstream() { - repo.Config.Unset(string.Format("branch.{0}.remote", branch.Name)); - repo.Config.Unset(string.Format("branch.{0}.merge", branch.Name)); + SetUpstreamRemote(string.Empty); + SetUpstreamBranch(string.Empty); } /// - /// Set the upstream information for the current branch. + /// Set the upstream information for the current branch. + /// + /// The upstream branch name is with respect to the current repository. + /// So, passing "refs/remotes/origin/master" will set the current branch + /// to track "refs/heads/master" on the origin. Passing in + /// "refs/heads/master" will result in the branch tracking the local + /// master branch. + /// /// - /// The upstream branch to track. - private void SetUpstream(string upStreamBranchName) + /// The remote branch to track (e.g. refs/remotes/origin/master). + private void SetUpstream(string upstreamBranchName) { if (branch.IsRemote) { @@ -68,26 +113,57 @@ private void SetUpstream(string upStreamBranchName) string remoteName; string branchName; - GetUpstreamInformation(upStreamBranchName, out remoteName, out branchName); + GetUpstreamInformation(upstreamBranchName, out remoteName, out branchName); + + SetUpstreamRemote(remoteName); + SetUpstreamBranch(branchName); + } + + /// + /// Set the upstream merge branch for the local branch. + /// + /// The merge branch in the upstream remote's namespace. + private void SetUpstreamBranch(string mergeBranchName) + { + string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.merge", branch.FriendlyName); - SetUpstreamTo(remoteName, branchName); + if (string.IsNullOrEmpty(mergeBranchName)) + { + repo.Config.Unset(configKey); + } + else + { + repo.Config.Set(configKey, mergeBranchName); + } } - private void SetUpstreamTo(string remoteName, string branchName) + /// + /// Set the upstream remote for the local branch. + /// + /// The name of the remote to set as the upstream branch. + private void SetUpstreamRemote(string remoteName) { - if (!remoteName.Equals(".", StringComparison.Ordinal)) + string configKey = string.Format(CultureInfo.InvariantCulture, "branch.{0}.remote", branch.FriendlyName); + + if (string.IsNullOrEmpty(remoteName)) { - // Verify that remote exists. - repo.Network.Remotes.RemoteForName(remoteName); + repo.Config.Unset(configKey); } + else + { + if (!remoteName.Equals(".", StringComparison.Ordinal)) + { + // Verify that remote exists. + using (repo.Network.Remotes.RemoteForName(remoteName)) { } + } - repo.Config.Set(string.Format("branch.{0}.remote", branch.Name), remoteName); - repo.Config.Set(string.Format("branch.{0}.merge", branch.Name), branchName); + repo.Config.Set(configKey, remoteName); + } } /// - /// Get the upstream remote and merge branch name from a Canonical branch name. - /// This will return the remote name (or ".") if a local branch for the remote name. + /// Get the upstream remote and merge branch name from a Canonical branch name. + /// This will return the remote name (or ".") if a local branch for the remote name. /// /// The canonical branch name to parse. /// The name of the corresponding remote the branch belongs to @@ -98,32 +174,25 @@ private void GetUpstreamInformation(string canonicalName, out string remoteName, remoteName = null; mergeBranchName = null; - const string localPrefix = "refs/heads/"; - const string remotePrefix = "refs/remotes/"; - - if (canonicalName.StartsWith(localPrefix, StringComparison.Ordinal)) + if (canonicalName.LooksLikeLocalBranch()) { remoteName = "."; mergeBranchName = canonicalName; } - else if (canonicalName.StartsWith(remotePrefix, StringComparison.Ordinal)) + else if (canonicalName.LooksLikeRemoteTrackingBranch()) { - using (ReferenceSafeHandle branchPtr = repo.Refs.RetrieveReferencePtr(canonicalName)) - { - remoteName = Proxy.git_branch_remote_name(repo.Handle, branchPtr); - } + remoteName = Proxy.git_branch_remote_name(repo.Handle, canonicalName, true); - Remote remote = repo.Network.Remotes.RemoteForName(remoteName); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repo.Handle, remote.Name, true)) + using (var remote = repo.Network.Remotes.RemoteForName(remoteName)) { - GitFetchSpecHandle fetchSpecPtr = Proxy.git_remote_fetchspec(remoteHandle); - mergeBranchName = Proxy.git_fetchspec_rtransform(fetchSpecPtr, canonicalName); + 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 new file mode 100644 index 000000000..1cf0d92e9 --- /dev/null +++ b/LibGit2Sharp/BuiltInFeatures.cs @@ -0,0 +1,39 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Flags to identify libgit2 compiled features. + /// + [Flags] + public enum BuiltInFeatures + { + /// + /// No optional features are compiled into libgit2. + /// + None = 0, + + /// + /// Threading support is compiled into libgit2. + /// + Threads = (1 << 0), + + /// + /// Support for remotes over the HTTPS protocol is compiled into + /// libgit2. + /// + Https = (1 << 1), + + /// + /// Support for remotes over the SSH protocol is compiled into + /// 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 f1d26ea40..304438be8 100644 --- a/LibGit2Sharp/ChangeKind.cs +++ b/LibGit2Sharp/ChangeKind.cs @@ -1,54 +1,64 @@ namespace LibGit2Sharp { /// - /// The kind of changes that a Diff can report. + /// The kind of changes that a Diff can report. /// public enum ChangeKind { /// - /// No changes detected. + /// No changes detected. /// Unmodified = 0, /// - /// The file was added. + /// The file was added. /// Added = 1, /// - /// The file was deleted. + /// The file was deleted. /// Deleted = 2, /// - /// The file content was modified. + /// The file content was modified. /// Modified = 3, /// - /// The file was renamed. + /// The file was renamed. /// Renamed = 4, /// - /// The file was copied. + /// The file was copied. /// Copied = 5, /// - /// The file is ignored in the workdir. + /// The file is ignored in the workdir. /// Ignored = 6, /// - /// The file is untracked in the workdir. + /// The file is untracked in the workdir. /// Untracked = 7, /// - /// The type (i.e. regular file, symlink, submodule, ...) - /// of the file was changed. + /// The type (i.e. regular file, symlink, submodule, ...) + /// of the file was changed. /// TypeChanged = 8, + + /// + /// Entry is unreadable. + /// + Unreadable = 9, + + /// + /// Entry is currently in conflict. + /// + Conflicted = 10, } } diff --git a/LibGit2Sharp/Changes.cs b/LibGit2Sharp/Changes.cs deleted file mode 100644 index 39f550995..000000000 --- a/LibGit2Sharp/Changes.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System.Diagnostics; -using System.Globalization; -using System.Text; - -namespace LibGit2Sharp -{ - /// - /// Base class for changes. - /// - [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class Changes - { - private readonly StringBuilder patchBuilder = new StringBuilder(); - - internal void AppendToPatch(string patch) - { - patchBuilder.Append(patch); - } - - /// - /// The number of lines added. - /// - public virtual int LinesAdded { get; internal set; } - - /// - /// The number of lines deleted. - /// - public virtual int LinesDeleted { get; internal set; } - - /// - /// The patch corresponding to these changes. - /// - public virtual string Patch - { - get { return patchBuilder.ToString(); } - } - - /// - /// Determines if at least one side of the comparison holds binary content. - /// - public virtual bool IsBinaryComparison { get; protected set; } - - private string DebuggerDisplay - { - get - { - return string.Format(CultureInfo.InvariantCulture, - @"{{+{0}, -{1}}}", LinesAdded, LinesDeleted); - } - } - } -} diff --git a/LibGit2Sharp/CheckoutCallbacks.cs b/LibGit2Sharp/CheckoutCallbacks.cs index 2c3a61095..dc03846bf 100644 --- a/LibGit2Sharp/CheckoutCallbacks.cs +++ b/LibGit2Sharp/CheckoutCallbacks.cs @@ -5,42 +5,77 @@ namespace LibGit2Sharp { /// - /// Class to handle the mapping between libgit2 progress_cb callback on the git_checkout_opts - /// structure to the CheckoutProgressHandler delegate. + /// Class to handle the mapping between libgit2 progress_cb callback on the git_checkout_opts + /// structure to the CheckoutProgressHandler delegate. /// internal class CheckoutCallbacks { /// - /// Managed delegate to call in response to checkout progress_cb callback. + /// The managed delegate (e.g. from library consumer) to be called in response to the checkout progress callback. /// private readonly CheckoutProgressHandler onCheckoutProgress; /// - /// Constructor to set up native callback for given managed delegate. + /// The managed delegate (e.g. from library consumer) to be called in response to the checkout notify callback. + /// + private readonly CheckoutNotifyHandler onCheckoutNotify; + + /// + /// Constructor to set up native callback for given managed delegate. /// /// delegate to call in response to checkout progress_cb - private CheckoutCallbacks(CheckoutProgressHandler onCheckoutProgress) + /// delegate to call in response to checkout notification callback. + private CheckoutCallbacks(CheckoutProgressHandler onCheckoutProgress, CheckoutNotifyHandler onCheckoutNotify) { this.onCheckoutProgress = onCheckoutProgress; + this.onCheckoutNotify = onCheckoutNotify; } /// - /// Generate a delegate matching the signature of the native progress_cb callback and wraps the delegate. + /// The method to pass for the native checkout progress callback. /// - /// that should be wrapped in the native callback. - /// The delegate with signature matching the expected native callback. - internal static progress_cb GenerateCheckoutCallbacks(CheckoutProgressHandler onCheckoutProgress) + public progress_cb CheckoutProgressCallback { - if (onCheckoutProgress == null) + get { + if (this.onCheckoutProgress != null) + { + return this.OnGitCheckoutProgress; + } + return null; } + } + + /// + /// The method to pass for the native checkout notify callback. + /// + public checkout_notify_cb CheckoutNotifyCallback + { + get + { + if (this.onCheckoutNotify != null) + { + return this.OnGitCheckoutNotify; + } - return new CheckoutCallbacks(onCheckoutProgress).OnGitCheckoutProgress; + return null; + } } /// - /// The delegate with a signature that matches the native checkout progress_cb function's signature. + /// Generate a delegate matching the signature of the native progress_cb callback and wraps the delegate. + /// + /// that should be wrapped in the native callback. + /// delegate to call in response to checkout notification callback. + /// The delegate with signature matching the expected native callback. + internal static CheckoutCallbacks From(CheckoutProgressHandler onCheckoutProgress, CheckoutNotifyHandler onCheckoutNotify) + { + return new CheckoutCallbacks(onCheckoutProgress, onCheckoutNotify); + } + + /// + /// The delegate with a signature that matches the native checkout progress_cb function's signature. /// /// The path that was updated. /// The number of completed steps. @@ -48,10 +83,31 @@ internal static progress_cb GenerateCheckoutCallbacks(CheckoutProgressHandler on /// Payload object. private void OnGitCheckoutProgress(IntPtr str, UIntPtr completedSteps, UIntPtr totalSteps, IntPtr payload) { - // Convert null strings into empty strings. - string path = (str != IntPtr.Zero) ? Utf8Marshaler.FromNative(str) : string.Empty; + if (onCheckoutProgress != null) + { + // Convert null strings into empty strings. + FilePath path = LaxFilePathMarshaler.FromNative(str) ?? FilePath.Empty; + + onCheckoutProgress(path.Native, (int)completedSteps, (int)totalSteps); + } + } + + private int OnGitCheckoutNotify( + CheckoutNotifyFlags why, + IntPtr pathPtr, + IntPtr baselinePtr, + IntPtr targetPtr, + IntPtr workdirPtr, + IntPtr payloadPtr) + { + bool result = true; + if (this.onCheckoutNotify != null) + { + FilePath path = LaxFilePathMarshaler.FromNative(pathPtr) ?? FilePath.Empty; + result = onCheckoutNotify(path.Native, why); + } - onCheckoutProgress(path, (int)completedSteps, (int)totalSteps); + return Proxy.ConvertResultToCancelFlag(result); } } } 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 new file mode 100644 index 000000000..9d53745e7 --- /dev/null +++ b/LibGit2Sharp/CheckoutFileConflictStrategy.cs @@ -0,0 +1,38 @@ +namespace LibGit2Sharp +{ + /// + /// Enum specifying what content checkout should write to disk + /// for conflicts. + /// + public enum CheckoutFileConflictStrategy + { + /// + /// Use the default behavior for handling file conflicts. This is + /// controlled by the merge.conflictstyle config option, and is "Merge" + /// if no option is explicitly set. + /// + Normal, + + /// + /// For conflicting files, checkout the "ours" (stage 2) version of + /// the file from the index. + /// + Ours, + + /// + /// For conflicting files, checkout the "theirs" (stage 3) version of + /// the file from the index. + /// + Theirs, + + /// + /// Write normal merge files for conflicts. + /// + Merge, + + /// + /// Write diff3 formated files for conflicts. + /// + Diff3 + } +} diff --git a/LibGit2Sharp/CheckoutModifiers.cs b/LibGit2Sharp/CheckoutModifiers.cs new file mode 100644 index 000000000..9ebad388f --- /dev/null +++ b/LibGit2Sharp/CheckoutModifiers.cs @@ -0,0 +1,22 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Options controlling Checkout behavior. + /// + [Flags] + public enum CheckoutModifiers + { + /// + /// No checkout flags - use default behavior. + /// + None = 0, + + /// + /// Proceed with checkout even if the index or the working tree differs from HEAD. + /// This will throw away local changes. + /// + Force, + } +} diff --git a/LibGit2Sharp/CheckoutNotificationOptions.cs b/LibGit2Sharp/CheckoutNotificationOptions.cs new file mode 100644 index 000000000..649695797 --- /dev/null +++ b/LibGit2Sharp/CheckoutNotificationOptions.cs @@ -0,0 +1,43 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Flags controlling checkout notification behavior. + /// + [Flags] + public enum CheckoutNotifyFlags + { + + /// + /// No checkout notification. + /// + None = 0, /* GIT_CHECKOUT_NOTIFY_NONE */ + + /// + /// Notify on conflicting paths. + /// + Conflict = (1 << 0), /* GIT_CHECKOUT_NOTIFY_CONFLICT */ + + /// + /// Notify about dirty files. These are files that do not need + /// an update, but no longer match the baseline. + /// + Dirty = (1 << 1), /* GIT_CHECKOUT_NOTIFY_DIRTY */ + + /// + /// Notify for files that will be updated. + /// + Updated = (1 << 2), /* GIT_CHECKOUT_NOTIFY_UPDATED */ + + /// + /// Notify for untracked files. + /// + Untracked = (1 << 3), /* GIT_CHECKOUT_NOTIFY_UNTRACKED */ + + /// + /// Notify about ignored file. + /// + Ignored = (1 << 4), /* GIT_CHECKOUT_NOTIFY_IGNORED */ + } +} diff --git a/LibGit2Sharp/CheckoutOptions.cs b/LibGit2Sharp/CheckoutOptions.cs index 9f3554222..010502007 100644 --- a/LibGit2Sharp/CheckoutOptions.cs +++ b/LibGit2Sharp/CheckoutOptions.cs @@ -1,22 +1,53 @@ -using System; +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// Options controlling Checkout behavior. + /// Collection of parameters controlling Checkout behavior. /// - [Flags] - public enum CheckoutOptions + public sealed class CheckoutOptions : IConvertableToGitCheckoutOpts { /// - /// No checkout flags - use default behavior. + /// Options controlling checkout behavior. /// - None = 0, + public CheckoutModifiers CheckoutModifiers { get; set; } /// - /// Proceed with checkout even if the index or the working tree differs from HEAD. - /// This will throw away local changes. + /// The flags specifying what conditions are + /// reported through the OnCheckoutNotify delegate. /// - Force, + public CheckoutNotifyFlags CheckoutNotifyFlags { 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; } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return CheckoutModifiers.HasFlag(CheckoutModifiers.Force) + ? CheckoutStrategy.GIT_CHECKOUT_FORCE + : CheckoutStrategy.GIT_CHECKOUT_SAFE; + } + } + + /// + /// Generate a object with the delegates + /// hooked up to the native callbacks. + /// + /// + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, OnCheckoutNotify); + } } } diff --git a/LibGit2Sharp/CherryPickOptions.cs b/LibGit2Sharp/CherryPickOptions.cs new file mode 100644 index 000000000..065e79bbb --- /dev/null +++ b/LibGit2Sharp/CherryPickOptions.cs @@ -0,0 +1,27 @@ +namespace LibGit2Sharp +{ + /// + /// Options controlling CherryPick behavior. + /// + 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() + { } + + /// + /// 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 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; } + } +} diff --git a/LibGit2Sharp/CherryPickResult.cs b/LibGit2Sharp/CherryPickResult.cs new file mode 100644 index 000000000..7e944baf7 --- /dev/null +++ b/LibGit2Sharp/CherryPickResult.cs @@ -0,0 +1,52 @@ +namespace LibGit2Sharp +{ + /// + /// Class to report the result of a cherry picked. + /// + public class CherryPickResult + { + /// + /// Needed for mocking purposes. + /// + protected CherryPickResult() + { } + + internal CherryPickResult(CherryPickStatus status, Commit commit = null) + { + Commit = commit; + Status = status; + } + + /// + /// The resulting commit of the cherry pick. + /// + /// This will return null if the cherry pick was not committed. + /// This can happen if: + /// 1) The cherry pick resulted in conflicts. + /// 2) The option to not commit on success is set. + /// + /// + public virtual Commit Commit { get; private set; } + + /// + /// The status of the cherry pick. + /// + public virtual CherryPickStatus Status { get; private set; } + } + + /// + /// The status of what happened as a result of a cherry-pick. + /// + public enum CherryPickStatus + { + /// + /// The commit was successfully cherry picked. + /// + CherryPicked, + + /// + /// The cherry pick resulted in conflicts. + /// + Conflicts + } +} diff --git a/LibGit2Sharp/CloneOptions.cs b/LibGit2Sharp/CloneOptions.cs new file mode 100644 index 000000000..12d47c9f3 --- /dev/null +++ b/LibGit2Sharp/CloneOptions.cs @@ -0,0 +1,86 @@ +using LibGit2Sharp.Core; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Options to define clone behavior + /// + public sealed class CloneOptions : IConvertableToGitCheckoutOpts + { + /// + /// 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() + { + Checkout = true; + } + + /// + /// True will result in a bare clone, false a full clone. + /// + public bool IsBare { get; set; } + + /// + /// If true, the origin's HEAD will be checked out. This only applies + /// to non-bare repositories. + /// + public bool Checkout { get; set; } + + /// + /// 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 bool RecurseSubmodules { get; set; } + + /// + /// Handler for checkout progress information. + /// + public CheckoutProgressHandler OnCheckoutProgress { get; set; } + + /// + /// Gets or sets the fetch options. + /// + public FetchOptions FetchOptions { get; } = new(); + + #region IConvertableToGitCheckoutOpts + + CheckoutCallbacks IConvertableToGitCheckoutOpts.GenerateCallbacks() + { + return CheckoutCallbacks.From(OnCheckoutProgress, null); + } + + CheckoutStrategy IConvertableToGitCheckoutOpts.CheckoutStrategy + { + get + { + return this.Checkout + ? CheckoutStrategy.GIT_CHECKOUT_SAFE + : CheckoutStrategy.GIT_CHECKOUT_NONE; + } + } + + CheckoutNotifyFlags IConvertableToGitCheckoutOpts.CheckoutNotifyFlags + { + get { return CheckoutNotifyFlags.None; } + } + + #endregion + } +} 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 48226e691..357567d8a 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -1,31 +1,34 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// A Commit + /// A Commit /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Commit : GitObject { - private readonly GitObjectLazyGroup group; + private readonly GitObjectLazyGroup group1; + private readonly GitObjectLazyGroup group2; private readonly ILazy lazyTree; private readonly ILazy lazyAuthor; private readonly ILazy lazyCommitter; private readonly ILazy lazyMessage; + private readonly ILazy lazyMessageShort; private readonly ILazy lazyEncoding; private readonly ParentsCollection parents; - private readonly Lazy lazyShortMessage; private readonly Lazy> lazyNotes; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Commit() { } @@ -33,96 +36,168 @@ protected Commit() internal Commit(Repository repo, ObjectId id) : base(repo, id) { - lazyTree = GitObjectLazyGroup.Singleton(this.repo, id, obj => new Tree(this.repo, Proxy.git_commit_tree_oid(obj), null)); + lazyTree = GitObjectLazyGroup.Singleton(this.repo, id, obj => new Tree(this.repo, Proxy.git_commit_tree_id(obj), null)); - group = new GitObjectLazyGroup(this.repo, id); - lazyAuthor = group.AddLazy(Proxy.git_commit_author); - lazyCommitter = group.AddLazy(Proxy.git_commit_committer); - lazyMessage = group.AddLazy(Proxy.git_commit_message); - lazyEncoding = group.AddLazy(RetrieveEncodingOf); + group1 = new GitObjectLazyGroup(this.repo, id); + lazyAuthor = group1.AddLazy(Proxy.git_commit_author); + lazyCommitter = group1.AddLazy(Proxy.git_commit_committer); + group2 = new GitObjectLazyGroup(this.repo, id); + lazyMessage = group2.AddLazy(Proxy.git_commit_message); + lazyMessageShort = group2.AddLazy(Proxy.git_commit_summary); + lazyEncoding = group2.AddLazy(RetrieveEncodingOf); - lazyShortMessage = new Lazy(ExtractShortMessage); lazyNotes = new Lazy>(() => RetrieveNotesOfCommit(id).ToList()); parents = new ParentsCollection(repo, id); } /// - /// Gets the pointed at by the in the . + /// Gets the pointed at by the in the . /// - /// The relative path to the from the working directory. - /// null if nothing has been found, the otherwise. + /// Path to the from the tree in this + /// null if nothing has been found, the otherwise. public virtual TreeEntry this[string relativePath] { get { return Tree[relativePath]; } } /// - /// Gets the commit message. + /// Gets the commit message. /// public virtual string Message { get { return lazyMessage.Value; } } /// - /// Gets the short commit message which is usually the first line of the commit. + /// Gets the short commit message which is usually the first line of the commit. /// - public virtual string MessageShort { get { return lazyShortMessage.Value; } } + public virtual string MessageShort { get { return lazyMessageShort.Value; } } /// - /// Gets the encoding of the message. + /// Gets the encoding of the message. /// public virtual string Encoding { get { return lazyEncoding.Value; } } /// - /// Gets the author of this commit. + /// Gets the author of this commit. /// public virtual Signature Author { get { return lazyAuthor.Value; } } /// - /// Gets the committer. + /// Gets the committer. /// public virtual Signature Committer { get { return lazyCommitter.Value; } } /// - /// Gets the Tree associated to this commit. + /// Gets the Tree associated to this commit. /// public virtual Tree Tree { get { return lazyTree.Value; } } /// - /// Gets the parents of this commit. This property is lazy loaded and can throw an exception if the commit no longer exists in the repo. + /// Gets the parents of this commit. This property is lazy loaded and can throw an exception if the commit no longer exists in the repo. /// public virtual IEnumerable Parents { get { return parents; } } /// - /// Gets The count of parent commits. + /// Gets the notes of this commit. /// - [Obsolete("This property will be removed in the next release. Please use Parents.Count() instead.")] - public virtual int ParentsCount { get { return Parents.Count(); } } + public virtual IEnumerable Notes { get { return lazyNotes.Value; } } + + private IEnumerable RetrieveNotesOfCommit(ObjectId oid) + { + return repo.Notes[oid]; + } + + private static string RetrieveEncodingOf(ObjectHandle obj) + { + string encoding = Proxy.git_commit_message_encoding(obj); + + return encoding ?? "UTF-8"; + } /// - /// Gets the notes of this commit. + /// Prettify a commit message + /// + /// Remove comment lines and trailing lines + /// /// - public virtual IEnumerable Notes { get { return lazyNotes.Value; } } + /// 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 ExtractShortMessage() + private string DebuggerDisplay { - if (Message == null) + get { - return string.Empty; //TODO: Add some test coverage + return string.Format(CultureInfo.InvariantCulture, + "{0} {1}", + Id.ToString(7), + MessageShort); } + } - return Message.Split('\n')[0]; + /// + /// 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); } - private IEnumerable RetrieveNotesOfCommit(ObjectId oid) + /// + /// 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 repo.Notes[oid]; + return Proxy.git_commit_extract_signature(repo.Handle, id, null); } - private static string RetrieveEncodingOf(GitObjectSafeHandle obj) + /// + /// 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) { - string encoding = Proxy.git_commit_message_encoding(obj); + 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 encoding ?? "UTF-8"; + return Proxy.git_commit_create_buffer(tree.repo.Handle, author, committer, message, tree, parents.ToArray()); } private class ParentsCollection : ICollection @@ -145,7 +220,7 @@ private ICollection RetrieveParentsOfCommit(Repository repo, ObjectId co for (uint i = 0; i < parentsCount; i++) { - ObjectId parentCommitId = Proxy.git_commit_parent_oid(obj.ObjectPtr, i); + ObjectId parentCommitId = Proxy.git_commit_parent_id(obj.ObjectPtr, i); parents.Add(new Commit(repo, parentCommitId)); } diff --git a/LibGit2Sharp/CommitFilter.cs b/LibGit2Sharp/CommitFilter.cs new file mode 100644 index 000000000..8997ca772 --- /dev/null +++ b/LibGit2Sharp/CommitFilter.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace LibGit2Sharp +{ + /// + /// Criteria used to filter out and order the commits of the repository when querying its history. + /// + public sealed class CommitFilter + { + /// + /// Initializes a new instance of . + /// + public CommitFilter() + { + SortBy = CommitSortStrategies.Time; + IncludeReachableFrom = "HEAD"; + FirstParentOnly = false; + } + + /// + /// The ordering strategy to use. + /// + /// By default, the commits are shown in reverse chronological order. + /// + /// + public CommitSortStrategies SortBy { get; set; } + + /// + /// A pointer to a commit object or a list of pointers to consider as starting points. + /// + /// Can be either a containing the sha or reference canonical name to use, + /// a , a , a , a , + /// a , an or even a mixed collection of all of the above. + /// By default, the will be used as boundary. + /// + /// + public object IncludeReachableFrom { get; set; } + + internal IList SinceList + { + get { return ToList(IncludeReachableFrom); } + } + + /// + /// A pointer to a commit object or a list of pointers which will be excluded (along with ancestors) from the enumeration. + /// + /// Can be either a containing the sha or reference canonical name to use, + /// a , a , a , a , + /// a , an or even a mixed collection of all of the above. + /// + /// + public object ExcludeReachableFrom { get; set; } + + internal IList UntilList + { + get { return ToList(ExcludeReachableFrom); } + } + + /// + /// Whether to limit the walk to each commit's first parent, instead of all of them + /// + public bool FirstParentOnly { get; set; } + + private static IList ToList(object obj) + { + var list = new List(); + + if (obj == null) + { + return list; + } + + var types = new[] + { + 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())) + { + list.Add(obj); + return list; + } + + list.AddRange(((IEnumerable)obj).Cast()); + return list; + } + } +} diff --git a/LibGit2Sharp/CommitLog.cs b/LibGit2Sharp/CommitLog.cs index 9abfc6988..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; @@ -9,44 +8,37 @@ namespace LibGit2Sharp { /// - /// A log of commits in a + /// A log of commits in a /// - public class CommitLog : IQueryableCommitLog + public sealed class CommitLog : IQueryableCommitLog { private readonly Repository repo; - private readonly Filter queryFilter; + private readonly CommitFilter queryFilter; /// - /// Needed for mocking purposes. + /// Initializes a new instance of the class. + /// The commits will be enumerated according in reverse chronological order. /// - protected CommitLog() - { } - - /// - /// Initializes a new instance of the class. - /// The commits will be enumerated according in reverse chronological order. - /// - /// The repository. + /// The repository. internal CommitLog(Repository repo) - : this(repo, new Filter()) - { - } + : this(repo, new CommitFilter()) + { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repository. + /// The repository. /// The filter to use in querying commits - internal CommitLog(Repository repo, Filter queryFilter) + internal CommitLog(Repository repo, CommitFilter queryFilter) { this.repo = repo; this.queryFilter = queryFilter; } /// - /// Gets the current sorting strategy applied when enumerating the log + /// Gets the current sorting strategy applied when enumerating the log /// - public virtual GitSortOptions SortedBy + public CommitSortStrategies SortedBy { get { return queryFilter.SortBy; } } @@ -54,23 +46,18 @@ public virtual GitSortOptions SortedBy #region IEnumerable Members /// - /// Returns an enumerator that iterates through the log. + /// Returns an enumerator that iterates through the log. /// - /// An object that can be used to iterate through the log. - public virtual IEnumerator GetEnumerator() + /// An object that can be used to iterate through the log. + public IEnumerator GetEnumerator() { - if ((repo.Info.IsEmpty) && queryFilter.SinceList.Any(o => PointsAtTheHead(o.ToString()))) // TODO: ToString() == fragile - { - return Enumerable.Empty().GetEnumerator(); - } - return new CommitEnumerator(repo, queryFilter); } /// - /// Returns an enumerator that iterates through the log. + /// Returns an enumerator that iterates through the log. /// - /// An object that can be used to iterate through the log. + /// An object that can be used to iterate through the log. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -79,105 +66,52 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Returns the list of commits of the repository matching the specified . + /// Returns the list of commits of the repository matching the specified . /// - /// The options used to control which commits will be returned. + /// The options used to control which commits will be returned. /// A list of commits, ready to be enumerated. - public virtual ICommitLog QueryBy(Filter filter) + 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); } - private static bool PointsAtTheHead(string shaOrRefName) - { - return ("HEAD".Equals(shaOrRefName, StringComparison.Ordinal) || "refs/heads/master".Equals(shaOrRefName, StringComparison.Ordinal)); - } - /// - /// Find the best possible common ancestor given two s. + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The first . - /// The second . - /// The common ancestor or null if none found. - public virtual Commit FindCommonAncestor(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"); - ObjectId id = Proxy.git_merge_base(repo.Handle, first, second); - - return id == null ? null : repo.Lookup(id); + return new FileHistory(repo, path); } /// - /// Find the best possible common ancestor given two or more . + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The s for which to find the common ancestor. - /// The common ancestor or null if none found. - public virtual Commit FindCommonAncestor(IEnumerable commits) + /// 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"); - - Commit ret = null; - int count = 0; - - foreach (var commit in commits) - { - if (commit == null) - { - throw new ArgumentException("Enumerable contains null at position: " + count.ToString(CultureInfo.InvariantCulture), "commits"); - } - - count++; - - if (count == 1) - { - ret = commit; - continue; - } - - ret = FindCommonAncestor(ret, commit); - if (ret == null) - { - break; - } - } - - if (count < 2) - { - throw new ArgumentException("The enumerable must contains at least two commits.", "commits"); - } + Ensure.ArgumentNotNull(path, "path"); + Ensure.ArgumentNotNull(filter, "filter"); - return ret; - } - - /// - /// 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 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. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - [Obsolete("This method will be removed in the next release. Please use Repository.Commit() instead.")] - public Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit) - { - return repo.Commit(message, author, committer, amendPreviousCommit); + 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, Filter filter) + public CommitEnumerator(Repository repo, CommitFilter filter) { this.repo = repo; handle = Proxy.git_revwalk_new(repo.Handle); @@ -186,6 +120,7 @@ public CommitEnumerator(Repository repo, Filter filter) Sort(filter.SortBy); Push(filter.SinceList); Hide(filter.UntilList); + FirstParentOnly(filter.FirstParentOnly); } #region IEnumerator Members @@ -232,11 +167,11 @@ 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) { - IEnumerable oids = RetrieveCommitOids(identifier).TakeWhile(o => o != null); + IEnumerable oids = repo.Committishes(identifier).TakeWhile(o => o != null); foreach (ObjectId actedOn in oids) { @@ -259,83 +194,36 @@ private void Hide(IList identifier) InternalHidePush(identifier, Proxy.git_revwalk_hide); } - private void Sort(GitSortOptions options) + private void Sort(CommitSortStrategies options) { Proxy.git_revwalk_sorting(handle, options); } - private ObjectId DereferenceToCommit(string identifier) - { - // TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees? - GitObject commit = repo.Lookup(identifier, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit); - - return commit != null ? commit.Id : null; - } - - private IEnumerable RetrieveCommitOids(object identifier) + private void FirstParentOnly(bool firstParent) { - if (identifier is string) - { - yield return DereferenceToCommit(identifier as string); - yield break; - } - - if (identifier is ObjectId) + if (firstParent) { - yield return DereferenceToCommit(((ObjectId)identifier).Sha); - yield break; - } - - if (identifier is Commit) - { - yield return ((Commit)identifier).Id; - yield break; - } - - if (identifier is TagAnnotation) - { - yield return DereferenceToCommit(((TagAnnotation)identifier).Target.Id.Sha); - yield break; - } - - if (identifier is Tag) - { - yield return DereferenceToCommit(((Tag)identifier).Target.Id.Sha); - yield break; - } - - if (identifier is Branch) - { - var branch = (Branch)identifier; - Ensure.GitObjectIsNotNull(branch.Tip, branch.CanonicalName); - - yield return branch.Tip.Id; - yield break; + Proxy.git_revwalk_simplify_first_parent(handle); } - - if (identifier is Reference) - { - yield return DereferenceToCommit(((Reference)identifier).CanonicalName); - yield break; - } - - if (identifier is IEnumerable) - { - var enumerable = (IEnumerable)identifier; - - foreach (object entry in enumerable) - { - foreach (ObjectId oid in RetrieveCommitOids(entry)) - { - yield return oid; - } - } - - yield break; - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unexpected kind of identifier '{0}'.", identifier)); } } } + + /// + /// Determines the finding strategy of merge base. + /// + public enum MergeBaseFindingStrategy + { + /// + /// Compute the best common ancestor between some commits to use in a three-way merge. + /// + /// When more than two commits are provided, the computation is performed between the first commit and a hypothetical merge commit across all the remaining commits. + /// + /// + Standard, + /// + /// Compute the best common ancestor of all supplied commits, in preparation for an n-way merge. + /// + Octopus, + } } diff --git a/LibGit2Sharp/CommitOptions.cs b/LibGit2Sharp/CommitOptions.cs new file mode 100644 index 000000000..ffd4c23d2 --- /dev/null +++ b/LibGit2Sharp/CommitOptions.cs @@ -0,0 +1,45 @@ +namespace LibGit2Sharp +{ + /// + /// Provides optional additional information to commit creation. + /// By default, a new commit will be created (instead of amending the + /// HEAD commit) and an empty commit which is unchanged from the current + /// HEAD is disallowed. + /// + public sealed class CommitOptions + { + /// + /// Initializes a new instance of the class. + /// + /// Default behavior: + /// The message is prettified. + /// No automatic removal of comments is performed. + /// + /// + public CommitOptions() + { + PrettifyMessage = true; + } + + /// + /// True to amend the current pointed at by , false otherwise. + /// + public bool AmendPreviousCommit { get; set; } + + /// + /// True to allow creation of an empty , false otherwise. + /// + public bool AllowEmptyCommit { get; set; } + + /// + /// True to prettify the message by stripping leading and trailing empty lines, trailing whitespace, and collapsing consecutive empty lines, false otherwise. + /// + public bool PrettifyMessage { get; set; } + + /// + /// The starting line char used to identify commentaries in the Commit message during the prettifying of the Commit message. If set (usually to '#'), all lines starting with this char will be removed from the message before the Commit is done. + /// This property will only be considered when PrettifyMessage is set to true. + /// + public char? CommentaryChar { get; set; } + } +} diff --git a/LibGit2Sharp/CommitRewriteInfo.cs b/LibGit2Sharp/CommitRewriteInfo.cs new file mode 100644 index 000000000..ca7399578 --- /dev/null +++ b/LibGit2Sharp/CommitRewriteInfo.cs @@ -0,0 +1,102 @@ +namespace LibGit2Sharp +{ + /// + /// Commit metadata when rewriting history + /// + public sealed class CommitRewriteInfo + { + /// + /// The author to be used for the new commit + /// + public Signature Author { get; set; } + + /// + /// The committer to be used for the new commit + /// + public Signature Committer { get; set; } + + /// + /// The message to be used for the new commit + /// + public string Message { get; set; } + + /// + /// Build a from the passed in + /// + /// The whose information is to be copied + /// A new object that matches the info for the . + public static CommitRewriteInfo From(Commit commit) + { + return new CommitRewriteInfo + { + 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); + } + + /// + /// 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 + /// 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, + Signature committer, + string message) + { + var cri = From(commit); + cri.Author = author ?? cri.Author; + cri.Committer = committer ?? cri.Committer; + cri.Message = message ?? cri.Message; + + return cri; + } + } +} diff --git a/LibGit2Sharp/CommitSortStrategies.cs b/LibGit2Sharp/CommitSortStrategies.cs new file mode 100644 index 000000000..46dc5a085 --- /dev/null +++ b/LibGit2Sharp/CommitSortStrategies.cs @@ -0,0 +1,39 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Determines the sorting strategy when iterating through the commits of the repository + /// + [Flags] + public enum CommitSortStrategies + { + /// + /// Sort the commits in no particular ordering; + /// this sorting is arbitrary, implementation-specific + /// and subject to change at any time. + /// + None = 0, + + /// + /// Sort the commits in topological order + /// (parents before children); this sorting mode + /// can be combined with time sorting. + /// + Topological = (1 << 0), + + /// + /// Sort the commits by commit time; + /// this sorting mode can be combined with + /// topological sorting. + /// + Time = (1 << 1), + + /// + /// Iterate through the commits in reverse + /// order; this sorting mode can be combined with + /// any of the above. + /// + Reverse = (1 << 2) + } +} diff --git a/LibGit2Sharp/CompareOptions.cs b/LibGit2Sharp/CompareOptions.cs new file mode 100644 index 000000000..fb4234439 --- /dev/null +++ b/LibGit2Sharp/CompareOptions.cs @@ -0,0 +1,54 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Options to define file comparison behavior. + /// + public sealed class CompareOptions + { + /// + /// Initializes a new instance of the class. + /// + public CompareOptions() + { + ContextLines = 3; + InterhunkLines = 0; + Algorithm = DiffAlgorithm.Myers; + } + + /// + /// The number of unchanged lines that define the boundary of a hunk (and to display before and after). + /// (Default = 3) + /// + public int ContextLines { get; set; } + + /// + /// The maximum number of unchanged lines between hunk boundaries before the hunks will be merged into a one. + /// (Default = 0) + /// + public int InterhunkLines { get; set; } + + /// + /// Options for rename detection. If null, the `diff.renames` configuration setting is used. + /// + public SimilarityOptions Similarity { get; set; } + + /// + /// 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 04747d79e..84a8a3e53 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -1,114 +1,215 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// Provides access to configuration variables for a repository. + /// Provides access to configuration variables for a repository. /// public class Configuration : IDisposable, - IEnumerable, 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. + /// Needed for mocking purposes. /// 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. + /// 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 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) + /// 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); } /// - /// Determines if a Git configuration file specific to the current interactive user has been found. + /// 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. + /// /// - [Obsolete("This property will be removed in the next release. Please use HasConfig() instead.")] - public virtual bool HasGlobalConfig + /// 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. + /// An instance of . + public static Configuration BuildFrom( + string repositoryConfigurationFileLocation, + string globalConfigurationFileLocation, + string xdgConfigurationFileLocation) { - get { return HasConfig(ConfigurationLevel.Global); } + return BuildFrom(repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, null); } /// - /// Determines if a system-wide Git configuration file has been found. + /// 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. + /// /// - [Obsolete("This property will be removed in the next release. Please use HasConfig() instead.")] - public virtual bool HasSystemConfig + /// 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) { - get { return HasConfig(ConfigurationLevel.System); } + return new Configuration(null, repositoryConfigurationFileLocation, globalConfigurationFileLocation, xdgConfigurationFileLocation, systemConfigurationFileLocation); } /// - /// Determines which configuration file has been found. + /// Determines which configuration file has been found. /// public virtual bool HasConfig(ConfigurationLevel level) { - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { return handle != null; } @@ -117,8 +218,8 @@ public virtual bool HasConfig(ConfigurationLevel level) #region IDisposable Members /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// Saves any open configuration files. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Saves any open configuration files. /// public void Dispose() { @@ -129,22 +230,55 @@ public void Dispose() #endregion /// - /// Unset a configuration variable (key and value). + /// 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) + /// The key to unset. + /// The configuration file which should be considered as the target of this operation + public virtual bool Unset(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { - Proxy.git_config_delete(h, key); + return Proxy.git_config_delete(h, key); } } /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// 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); + } + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// protected virtual void Dispose(bool disposing) { @@ -152,67 +286,129 @@ protected virtual void Dispose(bool disposing) } /// - /// Get a configuration value for a key. Keys are in the form 'section.name'. - /// - /// 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 - /// - global: the Git file specific to the current interactive user (usually in `$HOME/.gitconfig`) - /// - xdg: another Git file specific to the current interactive user (usually in `$HOME/.config/git/config`) - /// - system: the system-wide Git file + /// 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: /// - /// The first occurence of the key will be returned. - /// - /// - /// For example in order to get the value for this in a .git\config file: + /// + /// [difftool "kdiff3"] + /// path = c:/Program Files/KDiff3/kdiff3.exe + /// /// - /// - /// [core] - /// bare = true - /// + /// You would call: /// - /// 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'. + /// + /// 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 + /// - global: the Git file specific to the current interactive user (usually in `$HOME/.gitconfig`) + /// - xdg: another Git file specific to the current interactive user (usually in `$HOME/.config/git/config`) + /// - system: the system-wide Git file + /// + /// The first occurence of the key will be returned. + /// + /// + /// 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>("core.bare").Value; - /// - /// + /// + /// bool isBare = repo.Config.Get<bool>("core.bare").Value; + /// + /// /// - /// The configuration value type - /// The key + /// The configuration value type + /// The key /// The , or null if not set public virtual ConfigurationEntry Get(string key) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - return Proxy.git_config_get_entry(configHandle, key); + using (ConfigurationHandle snapshot = Snapshot()) + { + return Proxy.git_config_get_entry(snapshot, key); + } } /// - /// Get a configuration value for a key. Keys are in the form 'section.name'. - /// - /// For example in order to get the value for this in a .git\config file: + /// Get a configuration value for a key. Keys are in the form 'section.name'. + /// + /// For example in order to get the value for this in a .git\config file: /// - /// - /// [core] - /// bare = true - /// + /// + /// [core] + /// bare = true + /// /// - /// You would call: + /// You would call: /// - /// - /// bool isBare = repo.Config.Get<bool>("core.bare").Value; - /// - /// - /// - /// The configuration value type - /// The key - /// The configuration file into which the key should be searched for + /// + /// bool isBare = repo.Config.Get<bool>("core.bare").Value; + /// + /// + /// + /// The configuration value type + /// The key + /// The configuration file into which the key should be searched for /// The , or null if not set public virtual ConfigurationEntry Get(string key, ConfigurationLevel level) { Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle handle = RetrieveConfigurationHandle(level, false)) + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle handle = RetrieveConfigurationHandle(level, false, snapshot)) { if (handle == null) { @@ -224,27 +420,224 @@ public virtual ConfigurationEntry Get(string key, ConfigurationLevel level } /// - /// Set a configuration value for a key. Keys are in the form 'section.name'. - /// - /// For example in order to set the value for this in a .git\config file: + /// 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 + /// [test] + /// boolsetting = true /// - /// You would call: + /// You would call: /// - /// repo.Config.Set("test.boolsetting", true); - /// + /// repo.Config.Set("test.boolsetting", true); + /// /// - /// The configuration value type - /// 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) + /// 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'. + /// + /// 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 + /// The configuration file which should be considered as the target of this operation + public virtual void Set(string key, T value, ConfigurationLevel level) + { + Ensure.ArgumentNotNull(value, "value"); Ensure.ArgumentNotNullOrEmptyString(key, "key"); - using (ConfigurationSafeHandle h = RetrieveConfigurationHandle(level, true)) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, configHandle)) { if (!configurationTypedUpdater.ContainsKey(typeof(T))) { @@ -255,30 +648,103 @@ public virtual void Set(string key, T value, ConfigurationLevel level = Confi } } - private ConfigurationSafeHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound) + /// + /// 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) { - ConfigurationSafeHandle handle = null; - if (configHandle != null) + 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) + { + Ensure.ArgumentNotNullOrEmptyString(regexp, "regexp"); + + using (ConfigurationHandle snapshot = Snapshot()) + using (ConfigurationHandle h = RetrieveConfigurationHandle(level, true, snapshot)) + { + return Proxy.git_config_iterator_glob(h, regexp).ToList(); + } + } + + private ConfigurationHandle RetrieveConfigurationHandle(ConfigurationLevel level, bool throwIfStoreHasNotBeenFound, ConfigurationHandle fromHandle) + { + ConfigurationHandle handle = null; + if (fromHandle != null) { - handle = Proxy.git_config_open_level(configHandle, level); + handle = Proxy.git_config_open_level(fromHandle, level); } 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) }, @@ -286,32 +752,98 @@ private static Action GetUpdater(Act { typeof(string), GetUpdater(Proxy.git_config_set_string) }, }; - IEnumerator> IEnumerable>.GetEnumerator() + /// + /// Returns an enumerator that iterates through the configuration entries. + /// + /// An object that can be used to iterate through the configuration entries. + public virtual IEnumerator> GetEnumerator() { - return BuildConfigEntries().Cast>().GetEnumerator(); + return BuildConfigEntries().GetEnumerator(); } - [Obsolete("This method will be removed in the next release. Please use a different overload instead.")] - IEnumerator IEnumerable.GetEnumerator() + IEnumerator IEnumerable.GetEnumerator() { - return BuildConfigEntries().GetEnumerator(); + return ((IEnumerable>)this).GetEnumerator(); } - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() + private IEnumerable> BuildConfigEntries() { - return ((IEnumerable>)this).GetEnumerator(); + return Proxy.git_config_foreach(configHandle, BuildConfigEntry); } - private ICollection BuildConfigEntries() + internal static unsafe ConfigurationEntry BuildConfigEntry(IntPtr entryPtr) { - return Proxy.git_config_foreach(configHandle, entryPtr => + 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. 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 + /// - global: the Git file specific to the current interactive user (usually in `$HOME/.gitconfig`) + /// - xdg: another Git file specific to the current interactive user (usually in `$HOME/.config/git/config`) + /// - system: the system-wide Git file + /// + /// + /// The timestamp to use for the . + /// The signature or null if no user identity can be found in the configuration. + public virtual Signature BuildSignature(DateTimeOffset now) + { + 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 BuildSignatureOrThrow(DateTimeOffset now) + { + var signature = BuildSignature(now); + if (signature == null) { - var entry = (GitConfigEntry)Marshal.PtrToStructure(entryPtr, typeof(GitConfigEntry)); + 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"); + } - return new ConfigurationEntry(Utf8Marshaler.FromNative(entry.namePtr), - Utf8Marshaler.FromNative(entry.valuePtr), - (ConfigurationLevel)entry.level); - }); + return signature; + } + + 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 63ab9d2d9..13c153a2a 100644 --- a/LibGit2Sharp/ConfigurationEntry.cs +++ b/LibGit2Sharp/ConfigurationEntry.cs @@ -1,5 +1,4 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; using System.Globalization; namespace LibGit2Sharp @@ -27,7 +26,7 @@ public class ConfigurationEntry public virtual ConfigurationLevel Level { get; private set; } /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected ConfigurationEntry() { } @@ -38,7 +37,7 @@ protected ConfigurationEntry() /// The option name /// The option value /// The origin store - internal ConfigurationEntry(string key, T value, ConfigurationLevel level) + protected internal ConfigurationEntry(string key, T value, ConfigurationLevel level) { Key = key; Value = value; @@ -49,31 +48,8 @@ private string DebuggerDisplay { get { - return string.Format(CultureInfo.InvariantCulture, - "{0} = \"{1}\"", Key, Value); + return string.Format(CultureInfo.InvariantCulture, "{0} = \"{1}\"", Key, Value); } } } - - /// - /// Enumerated config option - /// - [Obsolete("This class will be removed in the next release. Please use ConfigurationEntry instead.")] - public class ConfigurationEntry : ConfigurationEntry - { - /// - /// Initializes a new instance of the class with a given key and value - /// - /// The option name - /// The option value - /// The origin store - internal ConfigurationEntry(string key, string value, ConfigurationLevel level) : base(key, value, level) - { } - - /// - /// Needed for mocking purposes. - /// - protected ConfigurationEntry() - { } - } } diff --git a/LibGit2Sharp/ConfigurationExtensions.cs b/LibGit2Sharp/ConfigurationExtensions.cs deleted file mode 100644 index 43c3900fe..000000000 --- a/LibGit2Sharp/ConfigurationExtensions.cs +++ /dev/null @@ -1,214 +0,0 @@ -using System; -using LibGit2Sharp.Core; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class ConfigurationExtensions - { - /// - /// Delete a configuration variable (key and value). - /// - /// The configuration being worked with. - /// The key to delete. - /// The configuration file which should be considered as the target of this operation - [Obsolete("This method will be removed in the next release. Please use Unset() instead.")] - public static void Delete(this Configuration config, string key, - ConfigurationLevel level = ConfigurationLevel.Local) - { - Ensure.ArgumentNotNullOrEmptyString(key, "key"); - - config.Unset(key, level); - } - - /// - /// Get a configuration value for a key. Keys are in the form 'section.name'. - /// - /// 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>("core.bare", false); - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The key - /// The default value - /// The configuration value, or defaultValue if not set - [Obsolete("This method will be removed in the next release. Please use a different overload instead.")] - public static T Get(this Configuration config, string key, T defaultValue) - { - Ensure.ArgumentNotNullOrEmptyString(key, "key"); - - var val = config.Get(key); - - return val == null ? defaultValue : val.Value; - } - - /// - /// Get a configuration value for a key. Keys are in the form 'section.name'. - /// - /// 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>("core", "bare", false); - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The first key part - /// The second key part - /// The default value - /// The configuration value, or defaultValue if not set - [Obsolete("This method will be removed in the next release. Please use a different overload instead.")] - public static T Get(this Configuration config, string firstKeyPart, string secondKeyPart, T defaultValue) - { - Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); - - return config.Get(new[] { firstKeyPart, secondKeyPart }, defaultValue); - } - - /// - /// 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", null); - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The first key part - /// The second key part - /// The third key part - /// The default value - /// The configuration value, or defaultValue if not set - [Obsolete("This method will be removed in the next release. Please use a different overload instead.")] - public static T Get(this Configuration config, string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue) - { - Ensure.ArgumentNotNullOrEmptyString(firstKeyPart, "firstKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(secondKeyPart, "secondKeyPart"); - Ensure.ArgumentNotNullOrEmptyString(thirdKeyPart, "secondKeyPart"); - - return config.Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }, defaultValue); - } - - /// - /// 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" }, false); - /// - /// - /// - /// The configuration value type - /// The configuration being worked with. - /// The key parts - /// The default value - /// The configuration value, or defaultValue if not set - [Obsolete("This method will be removed in the next release. Please use a different overload instead.")] - public static T Get(this Configuration config, string[] keyParts, T defaultValue) - { - Ensure.ArgumentNotNull(keyParts, "keyParts"); - - return config.Get(string.Join(".", keyParts), defaultValue); - } - - /// - /// 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, "secondKeyPart"); - - return config.Get(new[] { firstKeyPart, secondKeyPart, thirdKeyPart }); - } - } -} diff --git a/LibGit2Sharp/ConfigurationLevel.cs b/LibGit2Sharp/ConfigurationLevel.cs index 605d2f335..f0971a1c1 100644 --- a/LibGit2Sharp/ConfigurationLevel.cs +++ b/LibGit2Sharp/ConfigurationLevel.cs @@ -1,28 +1,38 @@ namespace LibGit2Sharp { /// - /// Specifies the level of configuration to use. + /// Specifies the level of configuration to use. /// public enum ConfigurationLevel { /// - /// The local .git/config of the current repository. + /// Worktree specific configuration file; $GIT_DIR/config.worktree /// - Local = 4, + Worktree = 6, /// - /// The global ~/.gitconfig of the current user. + /// The local .git/config of the current repository. /// - Global = 3, + Local = 5, /// - /// The global ~/.config/git/config of the current user. + /// The global ~/.gitconfig of the current user. /// - Xdg = 2, + Global = 4, /// - /// The system wide .gitconfig. + /// The global ~/.config/git/config of the current user. /// - System = 1, + Xdg = 3, + + /// + /// The system wide .gitconfig. + /// + System = 2, + + /// + /// Another system-wide configuration on Windows. + /// + ProgramData = 1, } } diff --git a/LibGit2Sharp/Conflict.cs b/LibGit2Sharp/Conflict.cs index e65200368..705f66d15 100644 --- a/LibGit2Sharp/Conflict.cs +++ b/LibGit2Sharp/Conflict.cs @@ -7,7 +7,7 @@ namespace LibGit2Sharp /// Represents a group of index entries that describe a merge conflict /// in the index. This is typically a set of ancestor, ours and theirs /// entries for a given path. - /// + /// /// Any side may be missing to reflect additions or deletions in the /// branches being merged. /// @@ -19,9 +19,9 @@ public class Conflict : IEquatable private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Ancestor, x => x.Ours, x => x.Theirs); - + /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Conflict() { } @@ -61,13 +61,13 @@ public virtual IndexEntry Theirs } /// - /// 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, + /// The to compare with + /// the current . + /// true if the specified is equal + /// to the current ; otherwise, /// false. public override bool Equals(object obj) { @@ -75,13 +75,13 @@ public override bool Equals(object obj) } /// - /// 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, + /// The to compare + /// with the current . + /// true if the specified is equal + /// to the current ; otherwise, /// false. public bool Equals(Conflict other) { @@ -89,7 +89,7 @@ public bool Equals(Conflict other) } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -98,10 +98,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(Conflict left, Conflict right) { @@ -109,10 +109,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(Conflict left, Conflict right) { diff --git a/LibGit2Sharp/ConflictCollection.cs b/LibGit2Sharp/ConflictCollection.cs index b350ec7ef..90d48fa33 100644 --- a/LibGit2Sharp/ConflictCollection.cs +++ b/LibGit2Sharp/ConflictCollection.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { @@ -14,21 +13,21 @@ namespace LibGit2Sharp /// public class ConflictCollection : IEnumerable { - private readonly Repository repo; + private readonly Index index; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected ConflictCollection() { } - internal ConflictCollection(Repository repo) + internal ConflictCollection(Index index) { - this.repo = repo; + this.index = index; } /// - /// Gets the for the + /// Gets the for the /// specified relative path. /// /// The relative path to query @@ -37,11 +36,35 @@ 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); } } - #region IEnumerable Members + /// + /// Get the that contains + /// the list of conflicts that have been resolved. + /// + public virtual IndexReucEntryCollection ResolvedConflicts + { + get + { + return new IndexReucEntryCollection(index); + } + } + + /// + /// Get the that contains + /// the list of paths involved in rename conflicts. + /// + public virtual IndexNameEntryCollection Names + { + get + { + return new IndexNameEntryCollection(index); + } + } + + #region IEnumerable Members private List AllConflicts() { @@ -49,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) { @@ -79,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)); } } @@ -95,18 +118,18 @@ private List AllConflicts() } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return AllConflicts().GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); diff --git a/LibGit2Sharp/ContentChangeStats.cs b/LibGit2Sharp/ContentChangeStats.cs new file mode 100644 index 000000000..8314888a0 --- /dev/null +++ b/LibGit2Sharp/ContentChangeStats.cs @@ -0,0 +1,30 @@ +namespace LibGit2Sharp +{ + /// + /// Holds a summary of a change to a single file. + /// + public class ContentChangeStats + { + /// + /// The number of lines added in the diff. + /// + public virtual int LinesAdded { get; private set; } + + /// + /// The number of lines deleted in the diff. + /// + public virtual int LinesDeleted { get; private set; } + + /// + /// For mocking. + /// + protected ContentChangeStats() + { } + + internal ContentChangeStats(int added, int deleted) + { + LinesAdded = added; + LinesDeleted = deleted; + } + } +} diff --git a/LibGit2Sharp/ContentChanges.cs b/LibGit2Sharp/ContentChanges.cs index 207dc9055..c4628f919 100644 --- a/LibGit2Sharp/ContentChanges.cs +++ b/LibGit2Sharp/ContentChanges.cs @@ -1,31 +1,83 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Holds the changes between two s. + /// Holds the changes between two s. /// - public class ContentChanges : Changes + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class ContentChanges { + private readonly StringBuilder patchBuilder = new StringBuilder(); + /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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) + { + this.IsBinaryComparison = isBinaryComparison; + } + + internal void AppendToPatch(string patch) + { + patchBuilder.Append(patch); + } + + /// + /// The number of lines added. + /// + public virtual int LinesAdded { get; internal set; } + + /// + /// The number of lines deleted. + /// + 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. + /// + public virtual string Patch + { + get { return patchBuilder.ToString(); } } - private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) + /// + /// Determines if at least one side of the comparison holds binary content. + /// + public virtual bool IsBinaryComparison { get; private set; } + + 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) { @@ -37,34 +89,36 @@ private int FileCallback(GitDiffDelta delta, float progress, IntPtr payload) return 0; } - private int HunkCallback(GitDiffDelta delta, GitDiffRange range, IntPtr header, UIntPtr headerlen, IntPtr payload) + private unsafe int HunkCallback(git_diff_delta* delta, GitDiffHunk hunk, IntPtr payload) { - string decodedContent = Utf8Marshaler.FromNative(header, (int)headerlen); + string decodedContent = LaxUtf8Marshaler.FromBuffer(hunk.Header, (int)hunk.HeaderLen); AppendToPatch(decodedContent); return 0; } - private int LineCallback(GitDiffDelta delta, GitDiffRange range, GitDiffLineOrigin lineorigin, IntPtr content, UIntPtr contentlen, IntPtr payload) + private unsafe int LineCallback(git_diff_delta* delta, GitDiffHunk hunk, GitDiffLine line, IntPtr payload) { - string decodedContent = Utf8Marshaler.FromNative(content, (int)contentlen); + string decodedContent = LaxUtf8Marshaler.FromNative(line.content, (int)line.contentLen); string prefix; - switch (lineorigin) + switch (line.lineOrigin) { case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: + AddedLines.Add(new Line(line.NewLineNo, decodedContent)); LinesAdded++; - prefix = Encoding.ASCII.GetString(new[] { (byte)lineorigin }); + 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)lineorigin }); + prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; case GitDiffLineOrigin.GIT_DIFF_LINE_CONTEXT: - prefix = Encoding.ASCII.GetString(new[] { (byte)lineorigin }); + prefix = Encoding.ASCII.GetString(new[] { (byte)line.lineOrigin }); break; default: @@ -76,5 +130,16 @@ private int LineCallback(GitDiffDelta delta, GitDiffRange range, GitDiffLineOrig AppendToPatch(decodedContent); return 0; } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + @"{{+{0}, -{1}}}", + LinesAdded, + LinesDeleted); + } + } } } diff --git a/LibGit2Sharp/Core/ArrayMarshaler.cs b/LibGit2Sharp/Core/ArrayMarshaler.cs new file mode 100644 index 000000000..4a37d241b --- /dev/null +++ b/LibGit2Sharp/Core/ArrayMarshaler.cs @@ -0,0 +1,40 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + internal class ArrayMarshaler : IDisposable + { + private readonly IntPtr[] ptrs; + + public ArrayMarshaler(T[] objs) + { + ptrs = new IntPtr[objs.Length]; + + for (var i = 0; i < objs.Length; i++) + { + IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf()); + ptrs[i] = ptr; + Marshal.StructureToPtr(objs[i], ptr, false); + } + } + + public int Count + { + get { return ptrs.Length; } + } + + public IntPtr[] ToArray() + { + return ptrs; + } + + public void Dispose() + { + foreach (var ptr in ptrs) + { + Marshal.FreeHGlobal(ptr); + } + } + } +} diff --git a/LibGit2Sharp/Core/Compat/EnumExtensions.cs b/LibGit2Sharp/Core/Compat/EnumExtensions.cs deleted file mode 100644 index beac535f8..000000000 --- a/LibGit2Sharp/Core/Compat/EnumExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Compat -{ - internal static class EnumExtensions - { - // Based on the following Stack Overflow post - // http://stackoverflow.com/questions/93744/most-common-c-sharp-bitwise-operations-on-enums/417217#417217 - // Reused with permission of Hugo Bonacci on Jan 04 2013. - public static bool HasFlag(this Enum enumInstance, T entry) - { - return ((int)(object)enumInstance & (int)(object)entry) == (int)(object)(entry); - } - } -} diff --git a/LibGit2Sharp/Core/Compat/Environment.cs b/LibGit2Sharp/Core/Compat/Environment.cs deleted file mode 100644 index 49b8e33c7..000000000 --- a/LibGit2Sharp/Core/Compat/Environment.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Compat -{ - /// - /// Provides information about, and means to manipulate, the current environment and platform. - /// - public static class Environment - { - /// - /// Determines whether the current process is a 64-bit process. - /// - public static bool Is64BitProcess - { - get { return IntPtr.Size == 8; } - } - } -} diff --git a/LibGit2Sharp/Core/Compat/Lazy.cs b/LibGit2Sharp/Core/Compat/Lazy.cs deleted file mode 100644 index 2891920c2..000000000 --- a/LibGit2Sharp/Core/Compat/Lazy.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Diagnostics; - -namespace LibGit2Sharp.Core.Compat -{ - /// - /// Provides support for lazy initialization. - /// - /// Specifies the type of object that is being lazily initialized. - [DebuggerStepThrough] - public class Lazy - { - private readonly Func evaluator; - private TType value; - private bool hasBeenEvaluated; - private readonly object padLock = new object(); - - /// - /// Initializes a new instance of the class. - /// - /// - public Lazy(Func evaluator) - { - Ensure.ArgumentNotNull(evaluator, "evaluator"); - - this.evaluator = evaluator; - } - - /// - /// Gets the lazily initialized value of the current instance. - /// - public TType Value - { - get { return Evaluate(); } - } - - private TType Evaluate() - { - if (!hasBeenEvaluated) - { - lock (padLock) - { - if (!hasBeenEvaluated) - { - value = evaluator(); - hasBeenEvaluated = true; - } - } - } - - return value; - } - } -} diff --git a/LibGit2Sharp/Core/Compat/Tuple.cs b/LibGit2Sharp/Core/Compat/Tuple.cs deleted file mode 100644 index cc863032c..000000000 --- a/LibGit2Sharp/Core/Compat/Tuple.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System.Collections.Generic; - -namespace LibGit2Sharp.Core.Compat -{ - /// - /// Represents a 2-tuple, or pair. - /// - /// The type of the tuple's first component. - /// The type of the tuple's second component. - public class Tuple - { - private readonly KeyValuePair kvp; - - /// - /// Initializes a new instance of the class. - /// - /// The value of the tuple's first component. - /// The value of the tuple's second component. - public Tuple(T1 item1, T2 item2) - { - kvp = new KeyValuePair(item1, item2); - } - - /// - /// Gets the value of the current object's second component. - /// - public T2 Item2 - { - get { return kvp.Value; } - } - - /// - /// Gets the value of the current object's first component. - /// - public T1 Item1 - { - get { return kvp.Key; } - } - - /// - /// Returns the hash code for the current object. - /// - /// A 32-bit signed integer hash code. - public override int GetHashCode() - { - return kvp.GetHashCode(); - } - - /// - /// Returns a value that indicates whether the current object is equal to a specified object. - /// - /// The object to compare with this instance. - /// true if the current instance is equal to the specified object; otherwise, false. - public override bool Equals(object obj) - { - if (!(obj is Tuple)) - { - return false; - } - return kvp.Equals(((Tuple)obj).kvp); - } - - - } -} diff --git a/LibGit2Sharp/Core/DisposableEnumerable.cs b/LibGit2Sharp/Core/DisposableEnumerable.cs deleted file mode 100644 index c885c3544..000000000 --- a/LibGit2Sharp/Core/DisposableEnumerable.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -namespace LibGit2Sharp.Core -{ - internal class DisposableEnumerable : IEnumerable, IDisposable where T : IDisposable - { - private readonly IList enumerable; - - public DisposableEnumerable(IList enumerable) - { - this.enumerable = enumerable; - } - - public int Count - { - get { return enumerable.Count; } - } - - public IEnumerator GetEnumerator() - { - return enumerable.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public void Dispose() - { - foreach (var entry in enumerable) - { - entry.Dispose(); - } - } - } -} diff --git a/LibGit2Sharp/Core/EncodingMarshaler.cs b/LibGit2Sharp/Core/EncodingMarshaler.cs new file mode 100644 index 000000000..cb02c649b --- /dev/null +++ b/LibGit2Sharp/Core/EncodingMarshaler.cs @@ -0,0 +1,169 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; + +namespace LibGit2Sharp.Core +{ + internal abstract class EncodingMarshaler : ICustomMarshaler + { + private readonly Encoding encoding; + + protected EncodingMarshaler(Encoding encoding) + { + this.encoding = encoding; + } + + #region ICustomMarshaler + + public void CleanUpManagedData(object managedObj) + { + } + + public virtual void CleanUpNativeData(IntPtr pNativeData) + { + Cleanup(pNativeData); + } + + public int GetNativeDataSize() + { + // Not a value type + return -1; + } + + public virtual IntPtr MarshalManagedToNative(object managedObj) + { + if (managedObj == null) + { + return IntPtr.Zero; + } + + var str = managedObj as string; + + if (str == null) + { + 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) + { + return FromNative(encoding, pNativeData); + } + + #endregion + + public static unsafe IntPtr FromManaged(Encoding encoding, string value) + { + if (encoding == null || value == null) + { + return IntPtr.Zero; + } + + int length = encoding.GetByteCount(value); + var buffer = (byte*)Marshal.AllocHGlobal(length + 1).ToPointer(); + + if (length > 0) + { + fixed (char* pValue = value) + { + encoding.GetBytes(pValue, value.Length, buffer, length); + } + } + + buffer[length] = 0; + + return new IntPtr(buffer); + } + + public static void Cleanup(IntPtr pNativeData) + { + if (pNativeData == IntPtr.Zero) + { + return; + } + + Marshal.FreeHGlobal(pNativeData); + } + + public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData) + { + return FromNative(encoding, (byte*)pNativeData); + } + + public static unsafe string FromNative(Encoding encoding, byte* pNativeData) + { + if (pNativeData == null) + { + return null; + } + + var start = (byte*)pNativeData; + byte* walk = start; + + // Find the end of the string + while (*walk != 0) + { + walk++; + } + + if (walk == start) + { + return string.Empty; + } + + return new string((sbyte*)pNativeData, 0, (int)(walk - start), encoding); + } + + public static unsafe string FromNative(Encoding encoding, IntPtr pNativeData, int length) + { + if (pNativeData == IntPtr.Zero) + { + return null; + } + + if (length == 0) + { + return string.Empty; + } + + return new string((sbyte*)pNativeData.ToPointer(), 0, length, encoding); + } + + public static string FromBuffer(Encoding encoding, byte[] buffer) + { + if (buffer == null) + { + return null; + } + + int length = 0; + int stop = buffer.Length; + + while (length < stop && + 0 != buffer[length]) + { + length++; + } + + return FromBuffer(encoding, buffer, length); + } + + public static string FromBuffer(Encoding encoding, byte[] buffer, int length) + { + Debug.Assert(buffer != null); + + if (length == 0) + { + return string.Empty; + } + + return encoding.GetString(buffer, 0, length); + } + } +} diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index d09ec6162..cd681e4ba 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -1,20 +1,22 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; namespace LibGit2Sharp.Core { /// - /// Ensure input parameters + /// Ensure input parameters /// [DebuggerStepThrough] internal static class Ensure { /// - /// Checks an argument to ensure it isn't null. + /// Checks an argument to ensure it isn't null. /// - /// The argument value to check. - /// The name of the argument. + /// The argument value to check. + /// The name of the argument. public static void ArgumentNotNull(object argumentValue, string argumentName) { if (argumentValue == null) @@ -24,68 +26,142 @@ public static void ArgumentNotNull(object argumentValue, string argumentName) } /// - /// Checks a string argument to ensure it isn't null or empty. + /// Checks an array argument to ensure it isn't null or empty. /// - /// The argument value to check. - /// The name of the argument. + /// The argument value to check. + /// The name of the argument. + public static void ArgumentNotNullOrEmptyEnumerable(IEnumerable argumentValue, string argumentName) + { + ArgumentNotNull(argumentValue, argumentName); + + if (!argumentValue.Any()) + { + throw new ArgumentException("Enumerable cannot be empty", argumentName); + } + } + + /// + /// Checks a string argument to ensure it isn't null or empty. + /// + /// The argument value to check. + /// The name of the argument. public static void ArgumentNotNullOrEmptyString(string argumentValue, string argumentName) { ArgumentNotNull(argumentValue, argumentName); - if (argumentValue.Trim().Length == 0) + if (string.IsNullOrWhiteSpace(argumentValue)) { throw new ArgumentException("String cannot be empty", argumentName); } } - private static void HandleError(int result) + /// + /// Checks a string argument to ensure it doesn't contain a zero byte. + /// + /// The argument value to check. + /// The name of the argument. + public static void ArgumentDoesNotContainZeroByte(string argumentValue, string argumentName) { - string errorMessage; - GitError error = NativeMethods.giterr_last().MarshalAsGitError(); - - if (error == null) + if (string.IsNullOrEmpty(argumentValue)) { - error = new GitError { Category = GitErrorCategory.Unknown, Message = IntPtr.Zero }; - errorMessage = "No error message has been provided by the native library"; + return; } - else + + int zeroPos = -1; + for (var i = 0; i < argumentValue.Length; i++) { - errorMessage = Utf8Marshaler.FromNative(error.Message); + if (argumentValue[i] == '\0') + { + zeroPos = i; + break; + } } - switch (result) + if (zeroPos == -1) { - case (int)GitErrorCode.BareRepo: - throw new BareRepositoryException(errorMessage, (GitErrorCode)result, error.Category); + return; + } - case (int)GitErrorCode.Exists: - throw new NameConflictException(errorMessage, (GitErrorCode)result, error.Category); + throw new ArgumentException( + string.Format(CultureInfo.InvariantCulture, + "Zero bytes ('\\0') are not allowed. A zero byte has been found at position {0}.", zeroPos), argumentName); + } - case (int)GitErrorCode.InvalidSpecification: - throw new InvalidSpecificationException(errorMessage, (GitErrorCode)result, error.Category); + /// + /// 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); + } + } - case (int)GitErrorCode.UnmergedEntries: - throw new UnmergedIndexEntriesException(errorMessage, (GitErrorCode)result, error.Category); + private static readonly Dictionary> + GitErrorsToLibGit2SharpExceptions = + new Dictionary> + { + { 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) }, + }; - case (int)GitErrorCode.NonFastForward: - throw new NonFastForwardException(errorMessage, (GitErrorCode)result, error.Category); + private static unsafe void HandleError(int result) + { + string errorMessage; + GitErrorCategory errorCategory = GitErrorCategory.Unknown; + GitError* error = NativeMethods.git_error_last(); - case (int)GitErrorCode.MergeConflict: - throw new MergeConflictException(errorMessage, (GitErrorCode)result, error.Category); + if (error == null) + { + errorMessage = "No error message has been provided by the native library"; + } + else + { + errorMessage = LaxUtf8Marshaler.FromNative(error->Message); + } - default: - throw new LibGit2SharpException(errorMessage, (GitErrorCode)result, error.Category); + Func exceptionBuilder; + if (!GitErrorsToLibGit2SharpExceptions.TryGetValue((GitErrorCode)result, out exceptionBuilder)) + { + exceptionBuilder = (m, c) => new LibGit2SharpException(m, c); } + + throw exceptionBuilder(errorMessage, errorCategory); } /// - /// Check that the result of a C call was successful - /// - /// The native function is expected to return strictly 0 for - /// success or a negative value in the case of failure. - /// + /// Check that the result of a C call was successful + /// + /// The native function is expected to return strictly 0 for + /// success or a negative value in the case of failure. + /// /// - /// The result to examine. + /// The result to examine. public static void ZeroResult(int result) { if (result == (int)GitErrorCode.Ok) @@ -97,17 +173,15 @@ public static void ZeroResult(int result) } /// - /// Check that the result of a C call that returns a boolean value - /// was successful - /// - /// The native function is expected to return strictly 0 for - /// success or a negative value in the case of failure. - /// + /// Check that the result of a C call returns a boolean value. + /// + /// The native function is expected to return strictly 0 or 1. + /// /// - /// The result to examine. + /// The result to examine. public static void BooleanResult(int result) { - if (result == (int)GitErrorCode.Ok || result == 1) + if (result == 0 || result == 1) { return; } @@ -116,14 +190,14 @@ public static void BooleanResult(int result) } /// - /// 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. - /// + /// Check that the result of a C call that returns an integer + /// value was successful. + /// + /// 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. + /// The result to examine. public static void Int32Result(int result) { if (result >= (int)GitErrorCode.Ok) @@ -135,11 +209,11 @@ public static void Int32Result(int result) } /// - /// Checks an argument by applying provided checker. + /// Checks an argument by applying provided checker. /// - /// The argument value to check. - /// The predicate which has to be satisfied - /// The name of the argument. + /// The argument value to check. + /// The predicate which has to be satisfied + /// The name of the argument. public static void ArgumentConformsTo(T argumentValue, Func checker, string argumentName) { if (checker(argumentValue)) @@ -150,6 +224,30 @@ public static void ArgumentConformsTo(T argumentValue, Func checker, throw new ArgumentException(argumentName); } + /// + /// 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) + { + if (argumentValue >= 0 && argumentValue <= uint.MaxValue) + { + return; + } + + throw new ArgumentException(argumentName); + } + + /// + /// 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) @@ -157,9 +255,14 @@ public static void GitObjectIsNotNull(GitObject gitObject, string identifier) return; } - throw new LibGit2SharpException(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/EnumExtensions.cs b/LibGit2Sharp/Core/EnumExtensions.cs index cef84e775..faaf42642 100644 --- a/LibGit2Sharp/Core/EnumExtensions.cs +++ b/LibGit2Sharp/Core/EnumExtensions.cs @@ -1,13 +1,12 @@ using System; using System.Collections.Generic; using System.Linq; -using LibGit2Sharp.Core.Compat; namespace LibGit2Sharp.Core { internal static class EnumExtensions { - public static bool HasAny(this Enum enumInstance, IEnumerable entries) + public static bool HasAny(this Enum enumInstance, IEnumerable entries) { return entries.Any(enumInstance.HasFlag); } diff --git a/LibGit2Sharp/Core/Epoch.cs b/LibGit2Sharp/Core/Epoch.cs deleted file mode 100644 index 101883f65..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 93e510fc6..af6afb048 100644 --- a/LibGit2Sharp/Core/FilePathMarshaler.cs +++ b/LibGit2Sharp/Core/FilePathMarshaler.cs @@ -1,22 +1,24 @@ using System; +using System.Globalization; using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { /// - /// This marshaler is to be used for capturing a UTF-8 string owned by libgit2 and - /// converting it to a managed FilePath instance. The marshaler will not attempt to - /// free the native pointer after conversion, because the memory is owned by libgit2. + /// This marshaler is to be used for capturing a UTF-8 string owned by libgit2 and + /// converting it to a managed FilePath instance. The marshaler will not attempt to + /// free the native pointer after conversion, because the memory is owned by libgit2. /// - /// Use this marshaler for return values, for example: - /// [return: MarshalAs(UnmanagedType.CustomMarshaler, - /// MarshalTypeRef = typeof(FilePathNoCleanupMarshaler))] + /// Use this marshaler for return values, for example: + /// [return: MarshalAs(UnmanagedType.CustomMarshaler, + /// MarshalCookie = UniqueId.UniqueIdentifier, + /// MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] /// - internal class FilePathNoCleanupMarshaler : FilePathMarshaler + internal class LaxFilePathNoCleanupMarshaler : LaxFilePathMarshaler { - private static readonly FilePathNoCleanupMarshaler staticInstance = new FilePathNoCleanupMarshaler(); + private static readonly LaxFilePathNoCleanupMarshaler staticInstance = new LaxFilePathNoCleanupMarshaler(); - public static new ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -31,48 +33,31 @@ public override void CleanUpNativeData(IntPtr pNativeData) } /// - /// This marshaler is to be used for sending managed FilePath instances to libgit2. - /// The marshaler will allocate a buffer in native memory to hold the UTF-8 string - /// and perform the encoding conversion using that buffer as the target. The pointer - /// received by libgit2 will be to this buffer. After the function call completes, the - /// native buffer is freed. + /// This marshaler is to be used for sending managed FilePath instances to libgit2. + /// The marshaler will allocate a buffer in native memory to hold the UTF-8 string + /// and perform the encoding conversion using that buffer as the target. The pointer + /// received by libgit2 will be to this buffer. After the function call completes, the + /// native buffer is freed. /// - /// Use this marshaler for function parameters, for example: - /// [DllImport(libgit2)] - /// internal static extern int git_index_open(out IndexSafeHandle index, - /// [MarshalAs(UnmanagedType.CustomMarshaler, - /// MarshalTypeRef = typeof(FilePathMarshaler))] FilePath indexpath); + /// Use this marshaler for function parameters, for example: + /// [DllImport(libgit2)] + /// internal static extern int git_index_open(out IndexSafeHandle index, + /// [MarshalAs(UnmanagedType.CustomMarshaler, + /// MarshalCookie = UniqueId.UniqueIdentifier, + /// MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath indexpath); /// - internal class FilePathMarshaler : ICustomMarshaler + internal class StrictFilePathMarshaler : StrictUtf8Marshaler { - private static readonly FilePathMarshaler staticInstance = new FilePathMarshaler(); + private static readonly StrictFilePathMarshaler staticInstance = new StrictFilePathMarshaler(); - public static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } #region ICustomMarshaler - public void CleanUpManagedData(Object managedObj) - { - } - - public virtual void CleanUpNativeData(IntPtr pNativeData) - { - if (IntPtr.Zero != pNativeData) - { - Marshal.FreeHGlobal(pNativeData); - } - } - - public int GetNativeDataSize() - { - // Not a value type - return -1; - } - - public IntPtr MarshalManagedToNative(Object managedObj) + public override IntPtr MarshalManagedToNative(object managedObj) { if (null == managedObj) { @@ -83,52 +68,63 @@ public IntPtr MarshalManagedToNative(Object managedObj) if (null == filePath) { - throw new MarshalDirectiveException("FilePathMarshaler must be used on a FilePath."); + throw new MarshalDirectiveException(string.Format(CultureInfo.InvariantCulture, + "{0} must be used on a FilePath.", + this.GetType().Name)); } return FromManaged(filePath); } - public Object MarshalNativeToManaged(IntPtr pNativeData) - { - return FromNative(pNativeData); - } - #endregion public static IntPtr FromManaged(FilePath filePath) { - if (null == filePath) + if (filePath == null) { return IntPtr.Zero; } - return Utf8Marshaler.FromManaged(filePath.Posix); + return StrictUtf8Marshaler.FromManaged(filePath.Posix); } + } - public static FilePath FromNative(IntPtr pNativeData) + /// + /// This marshaler is to be used for capturing a UTF-8 string allocated by libgit2 and + /// converting it to a managed FilePath instance. The marshaler will free the native pointer + /// after conversion. + /// + internal class LaxFilePathMarshaler : LaxUtf8Marshaler + { + private static readonly LaxFilePathMarshaler staticInstance = new LaxFilePathMarshaler(); + + public new static ICustomMarshaler GetInstance(string cookie) { - if (IntPtr.Zero == pNativeData) - { - return null; - } + return staticInstance; + } - if (0 == Marshal.ReadByte(pNativeData)) - { - return FilePath.Empty; - } + #region ICustomMarshaler - return Utf8Marshaler.FromNative(pNativeData); + public override object MarshalNativeToManaged(IntPtr pNativeData) + { + return FromNative(pNativeData); } - public static FilePath FromNative(IntPtr pNativeData, int length) + #endregion + + public new static FilePath FromNative(IntPtr pNativeData) { - if (0 == length) - { - return FilePath.Empty; - } + return LaxUtf8Marshaler.FromNative(pNativeData); + } - return Utf8Marshaler.FromNative(pNativeData, length); + 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 new file mode 100644 index 000000000..d484b0b4b --- /dev/null +++ b/LibGit2Sharp/Core/GitBlame.cs @@ -0,0 +1,89 @@ +using System; +using System.Globalization; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [Flags] + internal enum GitBlameOptionFlags + { + /// + /// Normal blame, the default + /// + GIT_BLAME_NORMAL = 0, + + /// + /// Track lines that have moved within a file (like `git blame -M`). + /// + GIT_BLAME_TRACK_COPIES_SAME_FILE = (1 << 0), + + /** Track lines that have moved across files in the same commit (like `git blame -C`). + * NOT IMPLEMENTED. */ + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_MOVES = (1 << 1), + + /// + /// Track lines that have been copied from another file that exists in the + /// same commit (like `git blame -CC`). Implies SAME_FILE. + /// + GIT_BLAME_TRACK_COPIES_SAME_COMMIT_COPIES = (1 << 2), + + /// + /// Track lines that have been copied from another file that exists in *any* + /// commit (like `git blame -CCC`). Implies SAME_COMMIT_COPIES. + /// + GIT_BLAME_TRACK_COPIES_ANY_COMMIT_COPIES = (1 << 3), + + /// + /// Restrict the search of commits to those reachable + /// following only the first parents. + /// + GIT_BLAME_FIRST_PARENT = (1 << 4), + } + + [StructLayout(LayoutKind.Sequential)] + internal class git_blame_options + { + public uint version = 1; + public GitBlameOptionFlags flags; + + 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 unsafe struct git_blame_hunk + { + public UIntPtr lines_in_hunk; + + public git_oid final_commit_id; + public UIntPtr final_start_line_number; + public git_signature* final_signature; + + public git_oid orig_commit_id; + public char* orig_path; + public UIntPtr orig_start_line_number; + public git_signature* orig_signature; + + public byte boundary; + } + + internal static class BlameStrategyExtensions + { + public static GitBlameOptionFlags ToGitBlameOptionFlags(this BlameStrategy strategy) + { + switch (strategy) + { + case BlameStrategy.Default: + return GitBlameOptionFlags.GIT_BLAME_NORMAL; + + default: + throw new NotSupportedException(string.Format(CultureInfo.InvariantCulture, + "{0} is not supported at this time", + strategy)); + } + } + } +} diff --git a/LibGit2Sharp/Core/GitBranchType.cs b/LibGit2Sharp/Core/GitBranchType.cs index 6304715f7..f843c11da 100644 --- a/LibGit2Sharp/Core/GitBranchType.cs +++ b/LibGit2Sharp/Core/GitBranchType.cs @@ -7,5 +7,6 @@ internal enum GitBranchType { GIT_BRANCH_LOCAL = 1, GIT_BRANCH_REMOTE = 2, + GIT_BRANCH_ALL = GIT_BRANCH_LOCAL | GIT_BRANCH_REMOTE, } } diff --git a/LibGit2Sharp/Core/GitBuf.cs b/LibGit2Sharp/Core/GitBuf.cs new file mode 100644 index 000000000..19b1328b9 --- /dev/null +++ b/LibGit2Sharp/Core/GitBuf.cs @@ -0,0 +1,18 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core.Handles +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitBuf : IDisposable + { + public IntPtr ptr; + public UIntPtr asize; + public UIntPtr size; + + public void Dispose() + { + 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 159ba5810..053258565 100644 --- a/LibGit2Sharp/Core/GitCheckoutOpts.cs +++ b/LibGit2Sharp/Core/GitCheckoutOpts.cs @@ -7,24 +7,24 @@ namespace LibGit2Sharp.Core internal enum CheckoutStrategy { /// - /// Default is a dry run, no actual updates. + /// Default is a dry run, no actual updates. /// GIT_CHECKOUT_NONE = 0, /// - /// Allow safe updates that cannot overwrite uncommited data. + /// Allow safe updates that cannot overwrite uncommited data. /// 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), @@ -56,19 +57,56 @@ internal enum CheckoutStrategy /// GIT_CHECKOUT_NO_REFRESH = (1 << 9), - ///Allow checkout to skip unmerged files (NOT IMPLEMENTED) + ///Allow checkout to skip unmerged files GIT_CHECKOUT_SKIP_UNMERGED = (1 << 10), /// - /// For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) + /// For unmerged files, checkout stage 2 from index /// GIT_CHECKOUT_USE_OURS = (1 << 11), /// - /// For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) + /// For unmerged files, checkout stage 3 from index /// GIT_CHECKOUT_USE_THEIRS = (1 << 12), + /// + /// Treat pathspec as simple list of exact match file paths + /// + GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH = (1 << 13), + + /// + /// Ignore directories in use, they will be left empty + /// + GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES = (1 << 18), + + /// + /// Don't overwrite ignored files that exist in the checkout target + /// + GIT_CHECKOUT_DONT_OVERWRITE_IGNORED = (1 << 19), + + /// + /// Write normal merge files for conflicts + /// + GIT_CHECKOUT_CONFLICT_STYLE_MERGE = (1 << 20), + + /// + /// Include common ancestor data in diff3 format files for conflicts + /// + 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) /// @@ -80,31 +118,27 @@ internal enum CheckoutStrategy GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1 << 17), } - [Flags] - internal enum NotifyFlags - { - GIT_CHECKOUT_NOTIFY_NONE = 0, - GIT_CHECKOUT_NOTIFY_CONFLICT = (1 << 0), - GIT_CHECKOUT_NOTIFY_DIRTY = (1 << 1), - GIT_CHECKOUT_NOTIFY_UPDATED = (1 << 2), - GIT_CHECKOUT_NOTIFY_UNTRACKED = (1 << 3), - GIT_CHECKOUT_NOTIFY_IGNORED = (1 << 4), - } - + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] internal delegate int checkout_notify_cb( - NotifyFlags why, + CheckoutNotifyFlags why, IntPtr path, IntPtr baseline, IntPtr target, 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 { @@ -117,15 +151,83 @@ internal struct GitCheckoutOpts public uint FileMode; public int FileOpenFlags; - public NotifyFlags notify_flags; + public CheckoutNotifyFlags notify_flags; public checkout_notify_cb notify_cb; public IntPtr notify_payload; public progress_cb progress_cb; public IntPtr progress_payload; - public UnSafeNativeMethods.git_strarray paths; + 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; + } + + /// + /// An inteface for objects that specify parameters from which a + /// GitCheckoutOpts struct can be populated. + /// + internal interface IConvertableToGitCheckoutOpts + { + CheckoutCallbacks GenerateCallbacks(); + + CheckoutStrategy CheckoutStrategy { get; } + + CheckoutNotifyFlags CheckoutNotifyFlags { get; } + } + + /// + /// This wraps an IConvertableToGitCheckoutOpts object and can tweak the + /// properties so that they are appropriate for a checkout performed as + /// part of a FastForward merge. Most properties are passthrough to the + /// wrapped object. + /// + internal class FastForwardCheckoutOptionsAdapter : IConvertableToGitCheckoutOpts + { + private IConvertableToGitCheckoutOpts internalOptions; + + internal FastForwardCheckoutOptionsAdapter(IConvertableToGitCheckoutOpts internalOptions) + { + this.internalOptions = internalOptions; + } + + /// + /// Passthrough to the wrapped object. + /// + /// + public CheckoutCallbacks GenerateCallbacks() + { + return internalOptions.GenerateCallbacks(); + } + + /// + /// There should be no resolvable conflicts in a FastForward merge. + /// Just perform checkout with the safe checkout strategy. + /// + public CheckoutStrategy CheckoutStrategy + { + get + { + return CheckoutStrategy.GIT_CHECKOUT_SAFE; + } + } + + /// + /// Passthrough to the wrapped object. + /// + /// + public CheckoutNotifyFlags CheckoutNotifyFlags + { + get { return internalOptions.CheckoutNotifyFlags; } + } } } diff --git a/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs new file mode 100644 index 000000000..0fba82754 --- /dev/null +++ b/LibGit2Sharp/Core/GitCheckoutOptsWrapper.cs @@ -0,0 +1,86 @@ +using System; + +namespace LibGit2Sharp.Core +{ + /// + /// A wrapper around the native GitCheckoutOpts structure. This class is responsible + /// for the managed objects that the native code points to. + /// + internal class GitCheckoutOptsWrapper : IDisposable + { + /// + /// Create wrapper around from . + /// + /// Options to create native GitCheckoutOpts structure from. + /// Paths to checkout. + public GitCheckoutOptsWrapper(IConvertableToGitCheckoutOpts options, FilePath[] paths = null) + { + Callbacks = options.GenerateCallbacks(); + + if (paths != null) + { + PathArray = GitStrArrayManaged.BuildFrom(paths); + } + + Options = new GitCheckoutOpts + { + version = 1, + checkout_strategy = options.CheckoutStrategy, + progress_cb = Callbacks.CheckoutProgressCallback, + notify_cb = Callbacks.CheckoutNotifyCallback, + notify_flags = options.CheckoutNotifyFlags, + paths = PathArray.Array, + }; + } + + /// + /// Native struct to pass to libgit. + /// + public GitCheckoutOpts Options { get; set; } + + /// + /// The managed class mapping native callbacks into the + /// corresponding managed delegate. + /// + public CheckoutCallbacks Callbacks { get; private set; } + + /// + /// Keep the paths around so we can dispose them. + /// + private GitStrArrayManaged PathArray; + + public void Dispose() + { + PathArray.Dispose(); + } + + /// + /// Method to translate from to flags. + /// + internal static CheckoutStrategy CheckoutStrategyFromFileConflictStrategy(CheckoutFileConflictStrategy fileConflictStrategy) + { + CheckoutStrategy flags = default(CheckoutStrategy); + + switch (fileConflictStrategy) + { + 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; + } + + return flags; + } + } +} diff --git a/LibGit2Sharp/Core/GitCherryPickOptions.cs b/LibGit2Sharp/Core/GitCherryPickOptions.cs new file mode 100644 index 000000000..287ecacf0 --- /dev/null +++ b/LibGit2Sharp/Core/GitCherryPickOptions.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitCherryPickOptions + { + public uint Version = 1; + + // For merge commits, the "mainline" is treated as the parent + public uint Mainline = 0; + + public GitMergeOpts MergeOpts = new GitMergeOpts { Version = 1 }; + + public GitCheckoutOpts CheckoutOpts = new GitCheckoutOpts { version = 1 }; + } +} diff --git a/LibGit2Sharp/Core/GitCloneOptions.cs b/LibGit2Sharp/Core/GitCloneOptions.cs index 4b6c68322..1ad86c5c3 100644 --- a/LibGit2Sharp/Core/GitCloneOptions.cs +++ b/LibGit2Sharp/Core/GitCloneOptions.cs @@ -3,26 +3,30 @@ namespace LibGit2Sharp.Core { + internal enum GitCloneLocal + { + CloneLocalAuto, + CloneLocal, + CloneNoLocal, + CloneLocalNoLinks + } + [StructLayout(LayoutKind.Sequential)] - internal class GitCloneOptions + internal struct GitCloneOptions { - public uint Version = 1; + public uint Version; public GitCheckoutOpts CheckoutOpts; - public int Bare; - public NativeMethods.git_transfer_progress_callback TransferProgressCallback; - public IntPtr TransferProgressPayload; + public GitFetchOptions FetchOpts; - public IntPtr RemoteName; - public IntPtr PushUrl; - public IntPtr FetchSpec; - public IntPtr PushSpec; + public int Bare; + public GitCloneLocal Local; + public IntPtr CheckoutBranch; - public NativeMethods.git_cred_acquire_cb CredAcquireCallback; - public IntPtr CredAcquirePayload; + public IntPtr RepositoryCb; + public IntPtr RepositoryCbPayload; - public IntPtr Transport; - public GitRemoteCallbacks RemoteCallbacks; - public int RemoteAutotag; + public IntPtr RemoteCb; + public IntPtr RemoteCbPayload; } } 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 new file mode 100644 index 000000000..0ab1273e2 --- /dev/null +++ b/LibGit2Sharp/Core/GitCredentialType.cs @@ -0,0 +1,51 @@ +using System; + +namespace LibGit2Sharp.Core +{ + /// + /// Authentication type requested. + /// + [Flags] + internal enum GitCredentialType + { + /// + /// A plaintext username and password. + /// + UserPassPlaintext = (1 << 0), + + /// + /// A ssh key from disk. + /// + SshKey = (1 << 1), + + /// + /// A key with a custom signature function. + /// + SshCustom = (1 << 2), + + /// + /// A key for NTLM/Kerberos "default" credentials. + /// + Default = (1 << 3), + + /// + /// 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 d5c60ecf9..44679124d 100644 --- a/LibGit2Sharp/Core/GitDiff.cs +++ b/LibGit2Sharp/Core/GitDiff.cs @@ -7,227 +7,294 @@ namespace LibGit2Sharp.Core internal enum GitDiffOptionFlags { /// - /// Normal diff, the default. + /// Normal diff, the default /// GIT_DIFF_NORMAL = 0, + /* + * Options controlling which files will be in the diff + */ + /// - /// Reverse the sides of the diff. + /// Reverse the sides of the diff /// GIT_DIFF_REVERSE = (1 << 0), /// - /// Treat all files as text, disabling binary attributes and detection. + /// Include ignored files in the diff /// - GIT_DIFF_FORCE_TEXT = (1 << 1), + GIT_DIFF_INCLUDE_IGNORED = (1 << 1), /// - /// Ignore all whitespace. + /// Even with GIT_DIFF_INCLUDE_IGNORED, an entire ignored directory + /// will be marked with only a single entry in the diff; this flag + /// adds all files under the directory as IGNORED entries, too. /// - GIT_DIFF_IGNORE_WHITESPACE = (1 << 2), + GIT_DIFF_RECURSE_IGNORED_DIRS = (1 << 2), /// - /// Ignore changes in amount of whitespace. + /// Include untracked files in the diff /// - GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3), + GIT_DIFF_INCLUDE_UNTRACKED = (1 << 3), /// - /// Ignore whitespace at end of line. + /// Even with GIT_DIFF_INCLUDE_UNTRACKED, an entire untracked + /// directory will be marked with only a single entry in the diff + /// (a la what core Git does in `git status`); this flag adds *all* + /// files under untracked directories as UNTRACKED entries, too. /// - GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4), + GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 4), /// - /// Exclude submodules from the diff completely. + /// Include unmodified files in the diff /// - GIT_DIFF_IGNORE_SUBMODULES = (1 << 5), + GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 5), /// - /// Use the "patience diff" algorithm (currently unimplemented). + /// Normally, a type change between files will be converted into a + /// DELETED record for the old and an ADDED record for the new; this + /// options enabled the generation of TYPECHANGE delta records. /// - GIT_DIFF_PATIENCE = (1 << 6), + GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 6), /// - /// Include ignored files in the diff list. + /// Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still + /// generally show as a DELETED blob. This flag tries to correctly + /// label blob->tree transitions as TYPECHANGE records with new_file's + /// mode set to tree. Note: the tree SHA will not be available. /// - GIT_DIFF_INCLUDE_IGNORED = (1 << 7), + GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 7), /// - /// Include untracked files in the diff list. + /// Ignore file mode changes /// - GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8), + GIT_DIFF_IGNORE_FILEMODE = (1 << 8), /// - /// Include unmodified files in the diff list. + /// Treat all submodules as unmodified /// - GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9), + GIT_DIFF_IGNORE_SUBMODULES = (1 << 9), /// - /// Even with the GIT_DIFF_INCLUDE_UNTRACKED flag, when an untracked - /// directory is found, only a single entry for the directory is added - /// to the diff list; with this flag, all files under the directory will - /// be included, too. + /// Use case insensitive filename comparisons /// - GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10), + GIT_DIFF_IGNORE_CASE = (1 << 10), + /// - /// 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. + /// 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_DISABLE_PATHSPEC_MATCH = (1 << 11), + GIT_DIFF_INCLUDE_CASECHANGE = (1 << 11), /// - /// Use case insensitive filename comparisons. + /// 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. /// - GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12), + GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 12), /// - /// When generating patch text, include the content of untracked files. + /// Disable updating of the `binary` flag in delta records. This is + /// useful when iterating over a diff if you don't need hunk and data + /// callbacks and want to avoid having to load file completely. /// - GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13), + GIT_DIFF_SKIP_BINARY_CHECK = (1 << 13), /// - /// Disable updating of the `binary` flag in delta records. This is - /// useful when iterating over a diff if you don't need hunk and data - /// callbacks and want to avoid having to load file completely. + /// When diff finds an untracked directory, to match the behavior of + /// core Git, it scans the contents for IGNORED and UNTRACKED files. + /// If *all* contents are IGNORED, then the directory is IGNORED; if + /// any contents are not IGNORED, then the directory is UNTRACKED. + /// This is extra work that may not matter in many cases. This flag + /// turns off that scan and immediately labels an untracked directory + /// as UNTRACKED (changing the behavior to not match core Git). /// - GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14), + GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS = (1 << 14), /// - /// Normally, a type change between files will be converted into a - /// DELETED record for the old and an ADDED record for the new; this - /// options enabled the generation of TYPECHANGE delta records. + /// When diff finds a file in the working directory with stat + /// information different from the index, but the OID ends up being the + /// same, write the correct stat information into the index. Note: + /// without this flag, diff will always leave the index untouched. /// - GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15), + GIT_DIFF_UPDATE_INDEX = (1 << 15), /// - /// Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still - /// generally show as a DELETED blob. This flag tries to correctly - /// label blob->tree transitions as TYPECHANGE records with new_file's - /// mode set to tree. Note: the tree SHA will not be available. + /// Include unreadable files in the diff /// - GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16), + GIT_DIFF_INCLUDE_UNREADABLE = (1 << 16), /// - /// Ignore file mode changes. + /// Include unreadable files in the diff /// - GIT_DIFF_IGNORE_FILEMODE = (1 << 17), - } + GIT_DIFF_INCLUDE_UNREADABLE_AS_UNTRACKED = (1 << 17), - [StructLayout(LayoutKind.Sequential)] - internal class GitStrArrayIn : IDisposable - { - public IntPtr strings; - public uint size; + /* + * Options controlling how output will be generated + */ - public static GitStrArrayIn BuildFrom(FilePath[] paths) - { - var nbOfPaths = paths.Length; - var pathPtrs = new IntPtr[nbOfPaths]; + /// + /// 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 + /// + GIT_DIFF_FORCE_TEXT = (1 << 20), - for (int i = 0; i < nbOfPaths; i++) - { - var s = paths[i]; - pathPtrs[i] = FilePathMarshaler.FromManaged(s); - } + /// + /// Treat all files as binary, disabling text diffs + /// + GIT_DIFF_FORCE_BINARY = (1 << 21), - int dim = IntPtr.Size * nbOfPaths; + /// + /// Ignore all whitespace + /// + GIT_DIFF_IGNORE_WHITESPACE = (1 << 22), - IntPtr arrayPtr = Marshal.AllocHGlobal(dim); - Marshal.Copy(pathPtrs, 0, arrayPtr, nbOfPaths); + /// + /// Ignore changes in amount of whitespace + /// + GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 23), - return new GitStrArrayIn { strings = arrayPtr, size = (uint)nbOfPaths }; - } + /// + /// Ignore whitespace at end of line + /// + GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 24), - public void Dispose() - { - if (size == 0) - { - return; - } + /// + /// When generating patch text, include the content of untracked + /// files. This automatically turns on GIT_DIFF_INCLUDE_UNTRACKED but + /// it does not turn on GIT_DIFF_RECURSE_UNTRACKED_DIRS. Add that + /// flag if you want the content of every single UNTRACKED file. + /// + GIT_DIFF_SHOW_UNTRACKED_CONTENT = (1 << 25), - var nbOfPaths = (int)size; + /// + /// When generating output, include the names of unmodified files if + /// they are included in the git_diff. Normally these are skipped in + /// the formats that list files (e.g. name-only, name-status, raw). + /// Even with this, these will not be included in patch format. + /// + GIT_DIFF_SHOW_UNMODIFIED = (1 << 26), - var pathPtrs = new IntPtr[nbOfPaths]; - Marshal.Copy(strings, pathPtrs, 0, nbOfPaths); + /// + /// Use the "patience diff" algorithm + /// + GIT_DIFF_PATIENCE = (1 << 28), - for (int i = 0; i < nbOfPaths; i ++) - { - Marshal.FreeHGlobal(pathPtrs[i]); - } + /// + /// Take extra time to find minimal diff + /// + GIT_DIFF_MINIMAL = (1 << 29), - Marshal.FreeHGlobal(strings); - } + /// + /// Include the necessary deflate / delta information so that `git-apply` + /// can apply given diff information to binary files. + /// + 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 { public uint Version = 1; public GitDiffOptionFlags Flags; - public ushort ContextLines; - public ushort InterhunkLines; - // NB: These are char*s to UTF8 strings, finna marshal them by hand - public IntPtr OldPrefixString; - public IntPtr NewPrefixString; + /* options controlling which files are in the diff */ - public GitStrArrayIn PathSpec; - public Int64 MaxSize; + public SubmoduleIgnore IgnoreSubmodules; + public GitStrArrayManaged PathSpec; + public diff_notify_cb NotifyCallback; + public diff_progress_cb ProgressCallback; + public IntPtr Payload; - public IntPtr NotifyCallback; - public IntPtr NotifyPayload; + /* options controlling how to diff text is generated */ + + public uint ContextLines; + public uint InterhunkLines; + public ushort IdAbbrev; + public long MaxSize; + public IntPtr OldPrefixString; + public IntPtr NewPrefixString; public void Dispose() { - if (PathSpec == null) - { - return; - } - PathSpec.Dispose(); - - PathSpec.size = 0; } } [Flags] - internal enum GitDiffFileFlags + internal enum GitDiffFlags { - GIT_DIFF_FILE_VALID_OID = (1 << 0), - GIT_DIFF_FILE_FREE_PATH = (1 << 1), - GIT_DIFF_FILE_BINARY = (1 << 2), - GIT_DIFF_FILE_NOT_BINARY = (1 << 3), - GIT_DIFF_FILE_FREE_DATA = (1 << 4), - GIT_DIFF_FILE_UNMAP_DATA = (1 << 5), + 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 Oid; - public IntPtr Path; - public Int64 Size; - public GitDiffFileFlags Flags; + public git_oid Id; + public char* Path; + public long Size; + public GitDiffFlags Flags; public ushort Mode; + public ushort IdAbbrev; } [StructLayout(LayoutKind.Sequential)] - internal class GitDiffDelta + internal unsafe struct git_diff_delta { - public GitDiffFile OldFile; - public GitDiffFile NewFile; - public ChangeKind Status; - public uint Similarity; - public int Binary; + 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)] - internal class GitDiffRange + internal class GitDiffHunk { public int OldStart; public int OldLines; public int NewStart; public int NewLines; + public UIntPtr HeaderLen; + + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] + public byte[] Header; + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffLine + { + public GitDiffLineOrigin lineOrigin; + public int OldLineNo; + public int NewLineNo; + public int NumLines; + public UIntPtr contentLen; + public long contentOffset; + public IntPtr content; } enum GitDiffLineOrigin : byte @@ -243,4 +310,104 @@ enum GitDiffLineOrigin : byte GIT_DIFF_LINE_HUNK_HDR = 0x48, //'H', GIT_DIFF_LINE_BINARY = 0x42, //'B', } + + enum GitDiffFormat + { + 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 + } + + [Flags] + enum GitDiffFindFlags + { + // Obey `diff.renames`. Overridden by any other GIT_DIFF_FIND_... flag. + GIT_DIFF_FIND_BY_CONFIG = 0, + + // Look for renames? (`--find-renames`) + GIT_DIFF_FIND_RENAMES = (1 << 0), + // consider old side of modified for renames? (`--break-rewrites=N`) + GIT_DIFF_FIND_RENAMES_FROM_REWRITES = (1 << 1), + + // look for copies? (a la `--find-copies`) + GIT_DIFF_FIND_COPIES = (1 << 2), + // consider unmodified as copy sources? (`--find-copies-harder`) + GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED = (1 << 3), + + // mark large rewrites for split (`--break-rewrites=/M`) + GIT_DIFF_FIND_REWRITES = (1 << 4), + // actually split large rewrites into delete/add pairs + GIT_DIFF_BREAK_REWRITES = (1 << 5), + // mark rewrites for split and break into delete/add pairs + GIT_DIFF_FIND_AND_BREAK_REWRITES = + (GIT_DIFF_FIND_REWRITES | GIT_DIFF_BREAK_REWRITES), + + // find renames/copies for untracked items in working directory + GIT_DIFF_FIND_FOR_UNTRACKED = (1 << 6), + + // turn on all finding features + GIT_DIFF_FIND_ALL = (0x0ff), + + // measure similarity ignoring leading whitespace (default) + GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE = 0, + // measure similarity ignoring all whitespace + GIT_DIFF_FIND_IGNORE_WHITESPACE = (1 << 12), + // measure similarity including all data + GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE = (1 << 13), + // measure similarity only by comparing SHAs (fast and cheap) + GIT_DIFF_FIND_EXACT_MATCH_ONLY = (1 << 14), + + // do not break rewrites unless they contribute to a rename + GIT_DIFF_BREAK_REWRITES_FOR_RENAMES_ONLY = (1 << 15), + + // Remove any UNMODIFIED deltas after find_similar is done. + GIT_DIFF_FIND_REMOVE_UNMODIFIED = (1 << 16), + } + + [StructLayout(LayoutKind.Sequential)] + internal class GitDiffFindOptions + { + public uint Version = 1; + public GitDiffFindFlags Flags; + 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 cdd00d2a0..000000000 --- a/LibGit2Sharp/Core/GitDiffExtensions.cs +++ /dev/null @@ -1,14 +0,0 @@ -using LibGit2Sharp.Core.Compat; - -namespace LibGit2Sharp.Core -{ - internal static class GitDiffExtensions - { - public static bool IsBinary(this GitDiffDelta delta) - { - //TODO Fix the interop issue on amd64 and use GitDiffDelta.Binary - return delta.OldFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FILE_BINARY) - || delta.NewFile.Flags.HasFlag(GitDiffFileFlags.GIT_DIFF_FILE_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 d8d06676f..5fc4c7d57 100644 --- a/LibGit2Sharp/Core/GitErrorCategory.cs +++ b/LibGit2Sharp/Core/GitErrorCategory.cs @@ -3,6 +3,7 @@ namespace LibGit2Sharp.Core internal enum GitErrorCategory { Unknown = -1, + None, NoMemory, Os, Invalid, @@ -23,5 +24,18 @@ internal enum GitErrorCategory Thread, Stash, Checkout, + FetchHead, + Merge, + Ssh, + Filter, + Revert, + Callback, + CherryPick, + Describe, + Rebase, + Filesystem, + Patch, + Worktree, + Sha1 } } diff --git a/LibGit2Sharp/Core/GitErrorCode.cs b/LibGit2Sharp/Core/GitErrorCode.cs index 0b59fcf50..6180cc4a8 100644 --- a/LibGit2Sharp/Core/GitErrorCode.cs +++ b/LibGit2Sharp/Core/GitErrorCode.cs @@ -6,68 +6,134 @@ internal enum GitErrorCode Error = -1, /// - /// Input does not exist in the scope searched. + /// Input does not exist in the scope searched. /// NotFound = -3, /// - /// Input already exists in the processed scope. + /// Input already exists in the processed scope. /// Exists = -4, /// - /// The given short oid is ambiguous. + /// The given short oid is ambiguous. /// Ambiguous = -5, /// - /// Buffer related issue. + /// Buffer related issue. /// Buffer = -6, /// - /// Callback error. + /// Callback error. /// User = -7, /// - /// Operation cannot be performed against a bare repository. + /// Operation cannot be performed against a bare repository. /// BareRepo = -8, /// - /// Operation cannot be performed against an orphaned HEAD. + /// Operation cannot be performed against an orphaned HEAD. /// OrphanedHead = -9, /// - /// Operation cannot be performed against a not fully merged index. + /// Operation cannot be performed against a not fully merged index. /// UnmergedEntries = -10, /// - /// Push cannot be performed against the remote without losing commits. + /// Push cannot be performed against the remote without losing commits. /// NonFastForward = -11, /// - /// Input is not a valid specification. + /// Input is not a valid specification. /// InvalidSpecification = -12, /// - /// A conflicting change has been detected. + /// A conflicting change has been detected in the index + /// or working directory. /// - MergeConflict = -13, + Conflict = -13, /// - /// Skip and passthrough the given ODB backend. + /// A file operation failed because the file was locked. + /// + LockedFile = -14, + + /// + /// Reference value does not match expected. + /// + 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. /// PassThrough = -30, /// - /// There are no more entries left to iterate. + /// 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 f2fea81e3..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 oid; - 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 new file mode 100644 index 000000000..47a9b1bcb --- /dev/null +++ b/LibGit2Sharp/Core/GitIndexNameEntry.cs @@ -0,0 +1,13 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_index_name_entry + { + public char* ancestor; + public char* ours; + public char* theirs; + } +} diff --git a/LibGit2Sharp/Core/GitIndexReucEntry.cs b/LibGit2Sharp/Core/GitIndexReucEntry.cs new file mode 100644 index 000000000..bc98d50df --- /dev/null +++ b/LibGit2Sharp/Core/GitIndexReucEntry.cs @@ -0,0 +1,17 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_index_reuc_entry + { + public uint AncestorMode; + public uint OurMode; + public uint TheirMode; + 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 new file mode 100644 index 000000000..48675a2d0 --- /dev/null +++ b/LibGit2Sharp/Core/GitMergeOpts.cs @@ -0,0 +1,198 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitMergeOpts + { + public uint Version; + + public GitMergeFlag MergeTreeFlags; + + /// + /// Similarity to consider a file renamed. + /// + public uint RenameThreshold; + + /// + /// Maximum similarity sources to examine (overrides + /// 'merge.renameLimit' config (default 200) + /// + public uint TargetLimit; + + /// + /// Pluggable similarityMetric; pass IntPtr.Zero + /// to use internal metric. + /// + 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; + } + + /// + /// The results of `git_merge_analysis` indicate the merge opportunities. + /// + [Flags] + internal enum GitMergeAnalysis + { + /// + /// No merge is possible. (Unused.) + /// + GIT_MERGE_ANALYSIS_NONE = 0, + + /// + /// A "normal" merge; both HEAD and the given merge input have diverged + /// from their common ancestor. The divergent commits must be merged. + /// + GIT_MERGE_ANALYSIS_NORMAL = (1 << 0), + + /// + /// All given merge inputs are reachable from HEAD, meaning the + /// repository is up-to-date and no merge needs to be performed. + /// + GIT_MERGE_ANALYSIS_UP_TO_DATE = (1 << 1), + + /// + /// The given merge input is a fast-forward from HEAD and no merge + /// needs to be performed. Instead, the client can check out the + /// given merge input. + /// + 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). + /// + GIT_MERGE_ANALYSIS_UNBORN = (1 << 3), + } + + internal enum GitMergePreference + { + /// + /// No configuration was found that suggests a preferred behavior for + /// merge. + /// + GIT_MERGE_PREFERENCE_NONE = 0, + + /// + /// There is a `merge.ff=false` configuration setting, suggesting that + /// the user does not want to allow a fast-forward merge. + /// + GIT_MERGE_PREFERENCE_NO_FASTFORWARD = (1 << 0), + + /// + /// There is a `merge.ff=only` configuration setting, suggesting that + /// the user only wants fast-forward merges. + /// + GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY = (1 << 1), + } + + [Flags] + internal enum GitMergeFlag + { + /// + /// No options. + /// + 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), + + /// + /// Take extra time to find minimal diff + /// + GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7), + } +} diff --git a/LibGit2Sharp/Core/GitObjectLazyGroup.cs b/LibGit2Sharp/Core/GitObjectLazyGroup.cs index 410720c58..f00900837 100644 --- a/LibGit2Sharp/Core/GitObjectLazyGroup.cs +++ b/LibGit2Sharp/Core/GitObjectLazyGroup.cs @@ -1,20 +1,20 @@ using System; +using System.Diagnostics.CodeAnalysis; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp.Core { - internal class GitObjectLazyGroup : LazyGroup + internal class GitObjectLazyGroup : LazyGroup { - private readonly Repository repo; private readonly ObjectId id; public GitObjectLazyGroup(Repository repo, ObjectId id) + : base(repo) { - this.repo = repo; this.id = id; } - protected override void EvaluateInternal(Action evaluator) + protected override void EvaluateInternal(Action evaluator) { using (var osw = new ObjectSafeWrapper(id, repo.Handle)) { @@ -22,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 new file mode 100644 index 000000000..50d8412c2 --- /dev/null +++ b/LibGit2Sharp/Core/GitObjectType.cs @@ -0,0 +1,107 @@ +using System; +using System.Globalization; + +namespace LibGit2Sharp.Core +{ + /// + /// Underlying type of a + /// + internal enum GitObjectType + { + /// + /// Object can be of any type. + /// + Any = -2, + + /// + /// Object is invalid. + /// + Bad = -1, + + /// + /// Reserved for future use. + /// + Ext1 = 0, + + /// + /// A commit object. + /// + Commit = 1, + + /// + /// A tree (directory listing) object. + /// + Tree = 2, + + /// + /// A file revision object. + /// + Blob = 3, + + /// + /// An annotated tag object. + /// + Tag = 4, + + /// + /// Reserved for future use. + /// + Ext2 = 5, + + /// + /// A delta, base is given by an offset. + /// + OfsDelta = 6, + + /// + /// A delta, base is given by object id. + /// + RefDelta = 7 + } + + internal static class GitObjectTypeExtensions + { + public static TreeEntryTargetType ToTreeEntryTargetType(this GitObjectType type) + { + switch (type) + { + case GitObjectType.Commit: + return TreeEntryTargetType.GitLink; + + case GitObjectType.Tree: + return TreeEntryTargetType.Tree; + + case GitObjectType.Blob: + return TreeEntryTargetType.Blob; + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a TreeEntryTargetType.", + type)); + } + } + + public static ObjectType ToObjectType(this GitObjectType type) + { + switch (type) + { + case GitObjectType.Commit: + return ObjectType.Commit; + + case GitObjectType.Tree: + return ObjectType.Tree; + + case GitObjectType.Blob: + return ObjectType.Blob; + + case GitObjectType.Tag: + return ObjectType.Tag; + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a ObjectType.", + type)); + } + } + } +} diff --git a/LibGit2Sharp/Core/GitObjectTypeMap.cs b/LibGit2Sharp/Core/GitObjectTypeMap.cs deleted file mode 100644 index df33787e3..000000000 --- a/LibGit2Sharp/Core/GitObjectTypeMap.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace LibGit2Sharp.Core -{ - internal class GitObjectTypeMap : Dictionary - { - public new GitObjectType this[Type type] - { - get { return !ContainsKey(type) ? GitObjectType.Any : base[type]; } - } - } -} diff --git a/LibGit2Sharp/Core/GitOdbBackend.cs b/LibGit2Sharp/Core/GitOdbBackend.cs index 9a0c7b2ae..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; @@ -30,9 +30,12 @@ static GitOdbBackend() public writestream_callback WriteStream; public readstream_callback ReadStream; public exists_callback Exists; + 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. */ @@ -53,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, @@ -75,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, @@ -93,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, @@ -109,9 +115,10 @@ 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( - ref GitOid oid, IntPtr backend, + ref GitOid oid, IntPtr data, UIntPtr len, GitObjectType type); @@ -126,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); /// @@ -140,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, @@ -152,10 +161,29 @@ 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); + /// + /// The backend is passed a short OID and the number of characters in that short OID. + /// The backend is asked to return a value that indicates whether or not + /// the object exists in the backing store. The short OID might not be long enough to resolve + /// to just one object. In that case the backend should return GIT_EAMBIGUOUS. + /// + /// [out] If the call is successful, the backend will write the full OID if the object here. + /// [in] A pointer to the backend which is being asked to perform the task. + /// [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, + ref GitOid short_oid, + UIntPtr len); + /// /// The backend is passed a callback function and a void* to pass through to the callback. The backend is /// asked to iterate through all objects in the backing store, invoking the callback for each item. @@ -163,6 +191,7 @@ public delegate bool exists_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, @@ -172,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); @@ -181,8 +211,9 @@ 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( - ref GitOid oid, + IntPtr oid, IntPtr data); } } diff --git a/LibGit2Sharp/Core/GitOdbBackendStream.cs b/LibGit2Sharp/Core/GitOdbBackendStream.cs index 12bc3553a..14b126c7a 100644 --- a/LibGit2Sharp/Core/GitOdbBackendStream.cs +++ b/LibGit2Sharp/Core/GitOdbBackendStream.cs @@ -15,11 +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 long DeclaredSize; + public long ReceivedBytes; public read_callback Read; public write_callback Write; @@ -34,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( - out GitOid oid_p, - IntPtr stream); + [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 027335719..f466621b1 100644 --- a/LibGit2Sharp/Core/GitOid.cs +++ b/LibGit2Sharp/Core/GitOid.cs @@ -2,15 +2,26 @@ 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. + /// Represents a unique id in git which is the sha1 hash of this id's content. /// internal struct GitOid { /// - /// The raw binary 20 byte Id. + /// Number of bytes in the Id. + /// + public const int Size = 20; + + /// + /// The raw binary 20 byte Id. /// - [MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] + [MarshalAs(UnmanagedType.ByValArray, SizeConst = Size)] public byte[] Id; public static implicit operator ObjectId(GitOid oid) @@ -22,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 new file mode 100644 index 000000000..ac9a99e1e --- /dev/null +++ b/LibGit2Sharp/Core/GitPushOptions.cs @@ -0,0 +1,16 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + 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 123655ce8..4900ad562 100644 --- a/LibGit2Sharp/Core/GitRemoteCallbacks.cs +++ b/LibGit2Sharp/Core/GitRemoteCallbacks.cs @@ -4,7 +4,7 @@ namespace LibGit2Sharp.Core { /// - /// Structure for git_remote_callbacks + /// Structure for git_remote_callbacks /// [StructLayout(LayoutKind.Sequential)] internal struct GitRemoteCallbacks @@ -15,8 +15,28 @@ internal struct GitRemoteCallbacks internal NativeMethods.remote_completion_callback completion; + internal NativeMethods.git_cred_acquire_cb acquire_credentials; + + 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 2c6bce911..cbaf3c818 100644 --- a/LibGit2Sharp/Core/GitRemoteHead.cs +++ b/LibGit2Sharp/Core/GitRemoteHead.cs @@ -4,11 +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 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 new file mode 100644 index 000000000..f639a0d8d --- /dev/null +++ b/LibGit2Sharp/Core/GitRepositoryInitOptions.cs @@ -0,0 +1,59 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitRepositoryInitOptions : IDisposable + { + public uint Version = 1; + public GitRepositoryInitFlags Flags; + public int Mode; + public IntPtr WorkDirPath; + public IntPtr Description; + public IntPtr TemplatePath; + public IntPtr InitialHead; + public IntPtr OriginUrl; + + 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 */ + }; + + if (workdirPath != null) + { + Debug.Assert(!isBare); + + opts.WorkDirPath = StrictFilePathMarshaler.FromManaged(workdirPath); + } + + if (isBare) + { + opts.Flags |= GitRepositoryInitFlags.GIT_REPOSITORY_INIT_BARE; + } + + return opts; + } + + public void Dispose() + { + EncodingMarshaler.Cleanup(WorkDirPath); + WorkDirPath = IntPtr.Zero; + } + } + + [Flags] + internal enum GitRepositoryInitFlags + { + GIT_REPOSITORY_INIT_BARE = (1 << 0), + GIT_REPOSITORY_INIT_NO_REINIT = (1 << 1), + GIT_REPOSITORY_INIT_NO_DOTGIT_DIR = (1 << 2), + GIT_REPOSITORY_INIT_MKDIR = (1 << 3), + GIT_REPOSITORY_INIT_MKPATH = (1 << 4), + GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE = (1 << 5), + } +} diff --git a/LibGit2Sharp/Core/GitRevertOpts.cs b/LibGit2Sharp/Core/GitRevertOpts.cs new file mode 100644 index 000000000..3d6583a81 --- /dev/null +++ b/LibGit2Sharp/Core/GitRevertOpts.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitRevertOpts + { + public uint Version = 1; + + // For merge commits, the "mainline" is treated as the parent + public uint Mainline = 0; + + public GitMergeOpts MergeOpts = new GitMergeOpts { 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 new file mode 100644 index 000000000..d4ce6d728 --- /dev/null +++ b/LibGit2Sharp/Core/GitSmartSubtransport.cs @@ -0,0 +1,39 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitSmartSubtransport + { + static GitSmartSubtransport() + { + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); + } + + public action_callback Action; + public close_callback Close; + public free_callback Free; + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + /* The following static fields are not part of the structure definition. */ + + 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 new file mode 100644 index 000000000..c8ae4fde7 --- /dev/null +++ b/LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs @@ -0,0 +1,19 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitSmartSubtransportRegistration + { + public IntPtr SubtransportCallback; + public uint Rpc; + public uint Param; + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + public delegate int create_callback( + out IntPtr subtransport, + IntPtr owner, + IntPtr param); + } +} diff --git a/LibGit2Sharp/Core/GitSmartSubtransportStream.cs b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs new file mode 100644 index 000000000..ae371b980 --- /dev/null +++ b/LibGit2Sharp/Core/GitSmartSubtransportStream.cs @@ -0,0 +1,44 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitSmartSubtransportStream + { + static GitSmartSubtransportStream() + { + GCHandleOffset = Marshal.OffsetOf(nameof(GCHandle)).ToInt32(); + } + + public IntPtr SmartTransport; + + public read_callback Read; + public write_callback Write; + public free_callback Free; + + /* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */ + + public IntPtr GCHandle; + + /* The following static fields are not part of the structure definition. */ + + 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); + + [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 new file mode 100644 index 000000000..73e6547a8 --- /dev/null +++ b/LibGit2Sharp/Core/GitStatusEntry.cs @@ -0,0 +1,27 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /// + /// A status entry from libgit2. + /// + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct git_status_entry + { + /// + /// Calculated status of a filepath in the working directory considering the current and the . + /// + public FileStatus status; + + /// + /// The difference between the and . + /// + public git_diff_delta* head_to_index; + + /// + /// The difference between the and the working directory. + /// + public git_diff_delta* index_to_workdir; + } +} diff --git a/LibGit2Sharp/Core/GitStatusOptions.cs b/LibGit2Sharp/Core/GitStatusOptions.cs new file mode 100644 index 000000000..d577cefe6 --- /dev/null +++ b/LibGit2Sharp/Core/GitStatusOptions.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal class GitStatusOptions : IDisposable + { + public uint Version = 1; + + public GitStatusShow Show; + public GitStatusOptionFlags Flags; + + public GitStrArrayManaged PathSpec; + + public IntPtr Baseline = IntPtr.Zero; + + public void Dispose() + { + PathSpec.Dispose(); + } + } + + internal enum GitStatusShow + { + IndexAndWorkDir = 0, + IndexOnly = 1, + WorkDirOnly = 2, + } + + [Flags] + internal enum GitStatusOptionFlags + { + IncludeUntracked = (1 << 0), + IncludeIgnored = (1 << 1), + IncludeUnmodified = (1 << 2), + ExcludeSubmodules = (1 << 3), + RecurseUntrackedDirs = (1 << 4), + DisablePathspecMatch = (1 << 5), + RecurseIgnoredDirs = (1 << 6), + RenamesHeadToIndex = (1 << 7), + RenamesIndexToWorkDir = (1 << 8), + SortCaseSensitively = (1 << 9), + SortCaseInsensitively = (1 << 10), + RenamesFromRewrites = (1 << 11), + NoRefresh = (1 << 12), + UpdateIndex = (1 << 13), + IncludeUnreadable = (1 << 14), + IncludeUnreadableAsUntracked = (1 << 15), + } +} diff --git a/LibGit2Sharp/Core/GitStrArray.cs b/LibGit2Sharp/Core/GitStrArray.cs new file mode 100644 index 000000000..d2efca8b7 --- /dev/null +++ b/LibGit2Sharp/Core/GitStrArray.cs @@ -0,0 +1,28 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + [StructLayout(LayoutKind.Sequential)] + internal struct GitStrArray + { + /// + /// A pointer to an array of null-terminated strings. + /// + public IntPtr Strings; + + /// + /// The number of strings in the array. + /// + public UIntPtr Count; + + /// + /// Resets the GitStrArray to default values. + /// + public void Reset() + { + Strings = IntPtr.Zero; + Count = UIntPtr.Zero; + } + } +} diff --git a/LibGit2Sharp/Core/GitStrArrayManaged.cs b/LibGit2Sharp/Core/GitStrArrayManaged.cs new file mode 100644 index 000000000..320221fcf --- /dev/null +++ b/LibGit2Sharp/Core/GitStrArrayManaged.cs @@ -0,0 +1,62 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /// + /// A git_strarray where the string array and strings themselves were allocated + /// with LibGit2Sharp's allocator (Marshal.AllocHGlobal). + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GitStrArrayManaged : IDisposable + { + public GitStrArray Array; + + public static GitStrArrayManaged BuildFrom(string[] strings) + { + return BuildFrom(strings, StrictUtf8Marshaler.FromManaged); + } + + public static GitStrArrayManaged BuildFrom(FilePath[] paths) + { + return BuildFrom(paths, StrictFilePathMarshaler.FromManaged); + } + + private static GitStrArrayManaged BuildFrom(T[] input, Func marshaler) + { + var pointers = new IntPtr[input.Length]; + + for (int i = 0; i < input.Length; i++) + { + var item = input[i]; + pointers[i] = marshaler(item); + } + + var toReturn = new GitStrArrayManaged(); + + toReturn.Array.Strings = Marshal.AllocHGlobal(checked(IntPtr.Size * input.Length)); + Marshal.Copy(pointers, 0, toReturn.Array.Strings, input.Length); + toReturn.Array.Count = new UIntPtr((uint)input.Length); + + return toReturn; + } + + public void Dispose() + { + var count = checked((int)Array.Count.ToUInt32()); + + for (int i = 0; i < count; i++) + { + EncodingMarshaler.Cleanup(Marshal.ReadIntPtr(Array.Strings, i * IntPtr.Size)); + } + + if (Array.Strings != IntPtr.Zero) + { + Marshal.FreeHGlobal(Array.Strings); + } + + // Now that we've freed the memory, zero out the structure. + Array.Reset(); + } + } +} diff --git a/LibGit2Sharp/Core/GitStrArrayNative.cs b/LibGit2Sharp/Core/GitStrArrayNative.cs new file mode 100644 index 000000000..01cd18e6e --- /dev/null +++ b/LibGit2Sharp/Core/GitStrArrayNative.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; + +namespace LibGit2Sharp.Core +{ + /// + /// A git_strarray where the string array and strings themselves were allocated + /// with libgit2's allocator. Only libgit2 can free this git_strarray. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct GitStrArrayNative : IDisposable + { + public GitStrArray Array; + + /// + /// Enumerates each string from the array using the UTF-8 marshaler. + /// + public string[] ReadStrings() + { + var count = checked((int)Array.Count.ToUInt32()); + + string[] toReturn = new string[count]; + + for (int i = 0; i < count; i++) + { + toReturn[i] = LaxUtf8Marshaler.FromNative(Marshal.ReadIntPtr(Array.Strings, i * IntPtr.Size)); + } + + return toReturn; + } + + public void Dispose() + { + if (Array.Strings != IntPtr.Zero) + { + NativeMethods.git_strarray_free(ref Array); + } + + // Now that we've freed the memory, zero out the structure. + Array.Reset(); + } + } +} 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/GitTransferProgress.cs b/LibGit2Sharp/Core/GitTransferProgress.cs index b57dadf55..911028501 100644 --- a/LibGit2Sharp/Core/GitTransferProgress.cs +++ b/LibGit2Sharp/Core/GitTransferProgress.cs @@ -4,7 +4,7 @@ namespace LibGit2Sharp.Core { /// - /// Managed structure corresponding to git_transfer_progress native structure. + /// Managed structure corresponding to git_transfer_progress native structure. /// [StructLayout(LayoutKind.Sequential)] internal struct GitTransferProgress @@ -12,6 +12,9 @@ internal struct GitTransferProgress public uint total_objects; public uint indexed_objects; public uint received_objects; + public uint local_objects; + public uint total_deltas; + public uint indexed_deltas; public UIntPtr received_bytes; } } 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/ConfigurationSafeHandle.cs b/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs deleted file mode 100644 index 2393e3dac..000000000 --- a/LibGit2Sharp/Core/Handles/ConfigurationSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ConfigurationSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - Proxy.git_config_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/DiffListSafeHandle.cs b/LibGit2Sharp/Core/Handles/DiffListSafeHandle.cs deleted file mode 100644 index c78f31317..000000000 --- a/LibGit2Sharp/Core/Handles/DiffListSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class DiffListSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - Proxy.git_diff_list_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/DisposableExtensions.cs b/LibGit2Sharp/Core/Handles/DisposableExtensions.cs new file mode 100644 index 000000000..0941ffcda --- /dev/null +++ b/LibGit2Sharp/Core/Handles/DisposableExtensions.cs @@ -0,0 +1,17 @@ +using System; + +namespace LibGit2Sharp.Core.Handles +{ + internal static class DisposableExtensions + { + public static void SafeDispose(this IDisposable disposable) + { + if (disposable == null) + { + return; + } + + disposable.Dispose(); + } + } +} diff --git a/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs b/LibGit2Sharp/Core/Handles/GitConfigEntryHandle.cs deleted file mode 100644 index c5ac71ded..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 (GitConfigEntry)Marshal.PtrToStructure(handle, typeof(GitConfigEntry)); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs deleted file mode 100644 index 9a222a482..000000000 --- a/LibGit2Sharp/Core/Handles/GitErrorSafeHandle.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitErrorSafeHandle : NotOwnedSafeHandleBase - { - public GitError MarshalAsGitError() - { - return (GitError)Marshal.PtrToStructure(handle, typeof(GitError)); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/GitFetchSpecHandle.cs b/LibGit2Sharp/Core/Handles/GitFetchSpecHandle.cs deleted file mode 100644 index 1f795e001..000000000 --- a/LibGit2Sharp/Core/Handles/GitFetchSpecHandle.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class GitFetchSpecHandle : NotOwnedSafeHandleBase - { - } -} diff --git a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs b/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs deleted file mode 100644 index a25d8ecfe..000000000 --- a/LibGit2Sharp/Core/Handles/GitObjectSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class GitObjectSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - Proxy.git_object_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexEntrySafeHandle.cs deleted file mode 100644 index 5f5301941..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 (GitIndexEntry)Marshal.PtrToStructure(handle, typeof(GitIndexEntry)); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs deleted file mode 100644 index 53437b33a..000000000 --- a/LibGit2Sharp/Core/Handles/IndexSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class IndexSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 513aabdc2..000000000 --- a/LibGit2Sharp/Core/Handles/NoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class NoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 af9595043..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 ReleaseHandle() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs b/LibGit2Sharp/Core/Handles/NullIndexSafeHandle.cs deleted file mode 100644 index a6bbca84f..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 ReleaseHandle() - { - // Nothing to release - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs b/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs deleted file mode 100644 index 89791e459..000000000 --- a/LibGit2Sharp/Core/Handles/ObjectDatabaseSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ObjectDatabaseSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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/OidSafeHandle.cs b/LibGit2Sharp/Core/Handles/OidSafeHandle.cs deleted file mode 100644 index a88673a4b..000000000 --- a/LibGit2Sharp/Core/Handles/OidSafeHandle.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal class OidSafeHandle : NotOwnedSafeHandleBase - { - private GitOid? MarshalAsGitOid() - { - return (GitOid?)Marshal.PtrToStructure(handle, typeof(GitOid)); - } - - public ObjectId MarshalAsObjectId() - { - return MarshalAsGitOid(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/PushSafeHandle.cs b/LibGit2Sharp/Core/Handles/PushSafeHandle.cs deleted file mode 100644 index 9b6ad26c3..000000000 --- a/LibGit2Sharp/Core/Handles/PushSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class PushSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 d2ebb6147..000000000 --- a/LibGit2Sharp/Core/Handles/ReferenceSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class ReferenceSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - Proxy.git_reference_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs b/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs deleted file mode 100644 index d7c792deb..000000000 --- a/LibGit2Sharp/Core/Handles/RemoteSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RemoteSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 97950cc5d..000000000 --- a/LibGit2Sharp/Core/Handles/RepositorySafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RepositorySafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 23449c43c..000000000 --- a/LibGit2Sharp/Core/Handles/RevWalkerSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class RevWalkerSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 e51a7562c..000000000 --- a/LibGit2Sharp/Core/Handles/SafeHandleBase.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Diagnostics; -using System.Globalization; -using System.Runtime.InteropServices; - -namespace LibGit2Sharp.Core.Handles -{ - internal abstract class SafeHandleBase : SafeHandle - { -#if LEAKS - private readonly string trace; -#endif - - protected SafeHandleBase() - : base(IntPtr.Zero, true) - { -#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 - - public override bool IsInvalid - { - get { return (handle == IntPtr.Zero); } - } - - protected abstract override bool ReleaseHandle(); - } -} diff --git a/LibGit2Sharp/Core/Handles/SafeHandleExtensions.cs b/LibGit2Sharp/Core/Handles/SafeHandleExtensions.cs deleted file mode 100644 index 00a4b6ed7..000000000 --- a/LibGit2Sharp/Core/Handles/SafeHandleExtensions.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace LibGit2Sharp.Core.Handles -{ - internal static class SafeHandleExtensions - { - public static void SafeDispose(this IDisposable disposable) - { - if (disposable == null) - return; - - var handle = disposable as SafeHandleBase; - if (handle != null) - { - SafeDispose(handle); - return; - } - - disposable.Dispose(); - } - - public static void SafeDispose(this SafeHandleBase handle) - { - if (handle == null || handle.IsClosed || handle.IsInvalid) - { - return; - } - - handle.Dispose(); - } - } -} diff --git a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs b/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs deleted file mode 100644 index 1db9e1259..000000000 --- a/LibGit2Sharp/Core/Handles/SignatureSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class SignatureSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - Proxy.git_signature_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs b/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs deleted file mode 100644 index 5f477f6cc..000000000 --- a/LibGit2Sharp/Core/Handles/TreeBuilderSafeHandle.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace LibGit2Sharp.Core.Handles -{ - internal class TreeBuilderSafeHandle : SafeHandleBase - { - protected override bool ReleaseHandle() - { - 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 8ba4c9f20..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 ReleaseHandle() - { - Proxy.git_tree_entry_free(handle); - return true; - } - } -} diff --git a/LibGit2Sharp/Core/HistoryRewriter.cs b/LibGit2Sharp/Core/HistoryRewriter.cs new file mode 100644 index 000000000..094d5ca1c --- /dev/null +++ b/LibGit2Sharp/Core/HistoryRewriter.cs @@ -0,0 +1,321 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; + +namespace LibGit2Sharp.Core +{ + internal class HistoryRewriter + { + private readonly IRepository repo; + + private readonly HashSet targetedCommits; + private readonly Dictionary objectMap = new Dictionary(); + private readonly Dictionary refMap = new Dictionary(); + private readonly Queue rollbackActions = new Queue(); + + private readonly string backupRefsNamespace; + private readonly RewriteHistoryOptions options; + + public HistoryRewriter( + IRepository repo, + IEnumerable commitsToRewrite, + RewriteHistoryOptions options) + { + this.repo = repo; + this.options = options; + targetedCommits = new HashSet(commitsToRewrite); + + backupRefsNamespace = this.options.BackupRefsNamespace; + + if (!backupRefsNamespace.EndsWith("/", StringComparison.Ordinal)) + { + backupRefsNamespace += "/"; + } + } + + public void Execute() + { + var success = false; + try + { + // Find out which refs lead to at least one the commits + var refsToRewrite = repo.Refs.ReachableFrom(targetedCommits).ToList(); + + var filter = new CommitFilter + { + IncludeReachableFrom = refsToRewrite, + SortBy = CommitSortStrategies.Reverse | CommitSortStrategies.Topological + }; + + var commits = repo.Commits.QueryBy(filter); + foreach (var commit in commits) + { + 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: Rewrite refs/notes/* properly + if (reference.CanonicalName.StartsWith("refs/notes/")) + { + continue; + } + + RewriteReference(reference); + } + + success = true; + if (options.OnSucceeding != null) + { + options.OnSucceeding(); + } + } + catch (Exception ex) + { + try + { + if (!success && options.OnError != null) + { + options.OnError(ex); + } + } + finally + { + foreach (var action in rollbackActions) + { + action(); + } + } + + throw; + } + finally + { + rollbackActions.Clear(); + } + } + + private Reference RewriteReference(Reference reference) + { + // Has this target already been rewritten? + if (refMap.ContainsKey(reference)) + { + return refMap[reference]; + } + + var sref = reference as SymbolicReference; + if (sref != null) + { + 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, logMessage) => refs.UpdateTarget(old, + target.Id, + logMessage)); + } + + return reference; + } + + private delegate Reference ReferenceUpdater( + ReferenceCollection refs, + TRef origRef, + TTarget origTarget, + string logMessage) + where TRef : Reference + where TTarget : class; + + private Reference RewriteReference( + TRef oldRef, Func selectTarget, + Func rewriteTarget, + ReferenceUpdater updateTarget) + where TRef : Reference + where TTarget : class + { + var oldRefTarget = selectTarget(oldRef); + + string newRefName = oldRef.CanonicalName; + if (oldRef.IsTag && options.TagNameRewriter != null) + { + newRefName = Reference.TagPrefix + + options.TagNameRewriter(oldRef.CanonicalName.Substring(Reference.TagPrefix.Length), + false, + oldRef.TargetIdentifier); + } + + var newTarget = rewriteTarget(oldRefTarget); + + if (oldRefTarget.Equals(newTarget) && oldRef.CanonicalName == newRefName) + { + // The reference isn't rewritten + return oldRef; + } + + string backupName = backupRefsNamespace + oldRef.CanonicalName.Substring("refs/".Length); + + 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)); + } + + 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, "filter-branch: abort", true)); + return refMap[oldRef] = null; + } + + 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, false); + rollbackActions.Enqueue(() => repo.Refs.Rename(newRef, oldRef.CanonicalName, false)); + return refMap[oldRef] = movedRef; + } + + private void RewriteCommit(Commit commit, RewriteHistoryOptions options) + { + var newHeader = CommitRewriteInfo.From(commit); + var newTree = commit.Tree; + + // Find the new parents + var newParents = commit.Parents; + + if (targetedCommits.Contains(commit)) + { + // Get the new commit header + if (options.CommitHeaderRewriter != null) + { + newHeader = options.CommitHeaderRewriter(commit) ?? newHeader; + } + + if (options.CommitTreeRewriter != null) + { + // Get the new commit tree + var newTreeDefinition = options.CommitTreeRewriter(commit); + newTree = repo.ObjectDatabase.CreateTree(newTreeDefinition); + } + + // Retrieve new parents + if (options.CommitParentsRewriter != null) + { + newParents = options.CommitParentsRewriter(commit); + } + } + + // Create the new commit + var mappedNewParents = newParents + .Select(oldParent => objectMap.ContainsKey(oldParent) + ? objectMap[oldParent] as Commit + : oldParent) + .Where(newParent => newParent != null) + .ToList(); + + if (options.PruneEmptyCommits && + TryPruneEmptyCommit(commit, mappedNewParents, newTree)) + { + return; + } + + var newCommit = repo.ObjectDatabase.CreateCommit(newHeader.Author, + newHeader.Committer, + newHeader.Message, + newTree, + mappedNewParents, + options.PrettifyMessages); + + // Record the rewrite + objectMap[commit] = newCommit; + } + + private bool TryPruneEmptyCommit(Commit commit, IList mappedNewParents, Tree newTree) + { + var parent = mappedNewParents.Count > 0 ? mappedNewParents[0] : null; + + if (parent == null) + { + if (newTree.Count == 0) + { + objectMap[commit] = null; + return true; + } + } + else if (parent.Tree == newTree) + { + objectMap[commit] = parent; + return true; + } + + return false; + } + + private GitObject RewriteTarget(GitObject oldTarget) + { + // Has this target already been rewritten? + if (objectMap.ContainsKey(oldTarget)) + { + return objectMap[oldTarget]; + } + + Debug.Assert((oldTarget as Commit) == null); + + var annotation = oldTarget as TagAnnotation; + if (annotation == null) + { + //TODO: Probably a Tree or a Blob. This is not covered by any test + return oldTarget; + } + + // Recursively rewrite annotations if necessary + var newTarget = RewriteTarget(annotation.Target); + + string newName = annotation.Name; + + if (options.TagNameRewriter != null) + { + newName = options.TagNameRewriter(annotation.Name, true, annotation.Target.Sha); + } + + var newAnnotation = repo.ObjectDatabase.CreateTagAnnotation(newName, + newTarget, + annotation.Tagger, + annotation.Message); + objectMap[annotation] = newAnnotation; + return newAnnotation; + } + + private int ReferenceDepth(Reference reference) + { + var dref = reference as DirectReference; + return dref == null + ? 1 + ReferenceDepth(((SymbolicReference)reference).Target) + : 1; + } + } +} diff --git a/LibGit2Sharp/Core/LambdaEqualityHelper.cs b/LibGit2Sharp/Core/LambdaEqualityHelper.cs index 55ae7bcaf..80e826c02 100644 --- a/LibGit2Sharp/Core/LambdaEqualityHelper.cs +++ b/LibGit2Sharp/Core/LambdaEqualityHelper.cs @@ -47,7 +47,8 @@ public int GetHashCode(T instance) { foreach (Func accessor in equalityContributorAccessors) { - hashCode = (hashCode*397) ^ accessor(instance).GetHashCode(); + object item = accessor(instance); + hashCode = (hashCode * 397) ^ (item != null ? item.GetHashCode() : 0); } } diff --git a/LibGit2Sharp/Core/LazyGroup.cs b/LibGit2Sharp/Core/LazyGroup.cs index 00e93571d..bcd160290 100644 --- a/LibGit2Sharp/Core/LazyGroup.cs +++ b/LibGit2Sharp/Core/LazyGroup.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using LibGit2Sharp.Core.Compat; +using System.Diagnostics.CodeAnalysis; namespace LibGit2Sharp.Core { @@ -9,6 +9,12 @@ internal abstract class LazyGroup private readonly IList> evaluators = new List>(); private readonly object @lock = new object(); private bool evaluated; + protected readonly Repository repo; + + protected LazyGroup(Repository repo) + { + this.repo = repo; + } public ILazy AddLazy(Func func) { @@ -19,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; } @@ -38,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); } @@ -84,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/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 18d30e772..cbb850b16 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1,773 +1,1533 @@ -using System; -using System.Globalization; +using System; using System.IO; +#if NET using System.Reflection; +#endif using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; 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 = "git2"; - private static readonly LibraryLifetimeObject lifetimeObject; - - /// - /// 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 - { - // 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() { Ensure.ZeroResult(git_threads_init()); } - ~LibraryLifetimeObject() { git_threads_shutdown(); } - } + private const string libgit2 = NativeDllName.Name; + + // 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() { - if (!IsRunningOnLinux()) + if (Platform.IsRunningOnNetFramework() || Platform.IsRunningOnNetCore()) { - string originalAssemblypath = new Uri(Assembly.GetExecutingAssembly().EscapedCodeBase).LocalPath; + // 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); + } + } + } + } - string currentArchSubPath = "NativeBinaries/" + ProcessorArchitecture; + InitializeNativeLibrary(); + } - string path = Path.Combine(Path.GetDirectoryName(originalAssemblypath), currentArchSubPath); + private static string GetGlobalSettingsNativeLibraryPath() + { + string nativeLibraryDir = GlobalSettings.GetAndLockNativeLibraryPath(); - const string pathEnvVariable = "PATH"; - Environment.SetEnvironmentVariable(pathEnvVariable, - String.Format(CultureInfo.InvariantCulture, "{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable))); + if (nativeLibraryDir == null) + { + return null; } - // See LibraryLifetimeObject description. - lifetimeObject = new LibraryLifetimeObject(); + return Path.Combine(nativeLibraryDir, libgit2 + Platform.GetNativeLibraryExtension()); } - public static string ProcessorArchitecture +#if NETFRAMEWORK + private static bool TryUseNativeLibrary() => false; +#else + private static bool TryUseNativeLibrary() { - get + NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, ResolveDll); + + return true; + } + + private static IntPtr ResolveDll(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) + { + IntPtr handle = IntPtr.Zero; + + if (libraryName == libgit2) { - if (Compat.Environment.Is64BitProcess) + // Use GlobalSettings.NativeLibraryPath when set. + string nativeLibraryPath = GetGlobalSettingsNativeLibraryPath(); + + if (nativeLibraryPath != null && NativeLibrary.TryLoad(nativeLibraryPath, out handle)) + { + return handle; + } + + // Use Default DllImport resolution. + if (NativeLibrary.TryLoad(libraryName, assembly, searchPath, out handle)) { - return "amd64"; + return handle; } - return "x86"; + // 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; + } + } + } + } } + + return handle; } +#endif + + public const int RTLD_NOW = 0x002; + + [DllImport("libdl", EntryPoint = "dlopen")] + private static extern IntPtr LoadUnixLibrary(string path, int flags); + + [DllImport("kernel32", EntryPoint = "LoadLibrary")] + private static extern IntPtr LoadWindowsLibrary(string path); - private static bool IsRunningOnLinux() + // 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() { - // see http://mono-project.com/FAQ%3a_Technical#Mono_Platforms - var p = (int)Environment.OSVersion.Platform; - return (p == 4) || (p == 6) || (p == 128); + int initCounter; + try + { + } + 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(); + } + + // Configure the OpenSSL locking on the first initialization of the library in the current process. + if (initCounter == 1) + { + git_openssl_set_locking(); + } + } + + // Shutdown the native library in a finalizer. + private sealed class NativeShutdownObject : CriticalFinalizerObject + { + ~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 int git_blob_create_fromdisk( - ref GitOid id, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_blame_get_hunk_count(git_blame* blame); - [DllImport(libgit2)] - internal static extern int git_blob_create_fromworkdir( - ref GitOid id, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath relative_path); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_blame_hunk* git_blame_get_hunk_byindex( + git_blame* blame, uint index); - internal delegate int source_callback( - IntPtr content, - int max_length, - IntPtr data); + [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 int git_blob_create_fromchunks( - ref GitOid oid, - RepositorySafeHandle repositoryPtr, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath hintpath, - source_callback fileCallback, - IntPtr data); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_blame_free(git_blame* blame); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_from_disk( + ref GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath path); - [DllImport(libgit2)] - internal static extern IntPtr git_blob_rawcontent(GitObjectSafeHandle blob); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_create_from_workdir( + ref GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath relative_path); - [DllImport(libgit2)] - internal static extern Int64 git_blob_rawsize(GitObjectSafeHandle blob); + [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_branch_create( - out ReferenceSafeHandle ref_out, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string branch_name, - GitObjectSafeHandle target, // TODO: GitCommitSafeHandle? + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_blob_create_from_stream_commit( + ref GitOid oid, + IntPtr stream); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_blob_filtered_content( + GitBuf buf, + 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe IntPtr git_blob_rawcontent(git_object* blob); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe long git_blob_rawsize(git_object* blob); + + [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, + 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)] - internal static extern int git_branch_foreach( - RepositorySafeHandle repo, - GitBranchType branch_type, - branch_foreach_callback branch_cb, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_branch_iterator_free( + IntPtr iterator); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_branch_iterator_new( + out IntPtr iter_out, + IntPtr repo, + GitBranchType branch_type); + + [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); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_branch_next( + out IntPtr ref_out, + out GitBranchType type_out, + IntPtr iter); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_remote_name( + GitBuf buf, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string canonical_branch_name); + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int commit_signing_callback( + IntPtr signature, + IntPtr signature_field, + IntPtr commit_content, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_branch_move( - ReferenceSafeHandle reference, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string new_branch_name, - [MarshalAs(UnmanagedType.Bool)] bool force); + [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, + 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)] - internal static extern int git_branch_remote_name( - byte[] remote_name_out, - UIntPtr buffer_size, - RepositorySafeHandle repo, - ReferenceSafeHandle branch); - - [DllImport(libgit2)] - internal static extern int git_branch_tracking_name( - byte[] tracking_branch_name_out, // NB: This is more properly a StringBuilder, but it's UTF8 - UIntPtr buffer_size, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string referenceName); - - [DllImport(libgit2)] - internal static extern int git_checkout_tree( - RepositorySafeHandle repo, - GitObjectSafeHandle treeish, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_branch_upstream_name( + GitBuf buf, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string referenceName); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_buf_dispose(GitBuf buf); + + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string origin_url, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath workdir_path, - GitCloneOptions opts); + [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( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_from_ids( + out GitOid id, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string updateRef, + 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); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_commit_create_with_signature( out GitOid id, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string updateRef, - SignatureSafeHandle author, - SignatureSafeHandle committer, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string encoding, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, - GitObjectSafeHandle tree, - int parentCount, - [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 7)] [In] IntPtr[] parents); - - [DllImport(libgit2)] - [return : MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_commit_message(GitObjectSafeHandle commit); - - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_commit_message_encoding(GitObjectSafeHandle commit); - - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_parent_id(GitObjectSafeHandle commit, uint n); - - [DllImport(libgit2)] - internal static extern uint git_commit_parentcount(GitObjectSafeHandle commit); - - [DllImport(libgit2)] - internal static extern OidSafeHandle git_commit_tree_id(GitObjectSafeHandle commit); - - [DllImport(libgit2)] - internal static extern int git_config_delete_entry(ConfigurationSafeHandle cfg, string name); - - [DllImport(libgit2)] - internal static extern int git_config_find_global(byte[] global_config_path, UIntPtr length); - - [DllImport(libgit2)] - internal static extern int git_config_find_system(byte[] system_config_path, UIntPtr length); - - [DllImport(libgit2)] - internal static extern int git_config_find_xdg(byte[] xdg_config_path, UIntPtr length); - - [DllImport(libgit2)] - internal static extern void git_config_free(IntPtr cfg); - - [DllImport(libgit2)] - internal static extern int git_config_get_entry( - out GitConfigEntryHandle entry, - ConfigurationSafeHandle cfg, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name); - - [DllImport(libgit2)] - internal static extern int git_config_add_file_ondisk( - ConfigurationSafeHandle cfg, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path, + 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, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_commit_message(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_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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_commit_parentcount(git_object* commit); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_commit_tree_id(git_object* 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, 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, 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_find_system(GitBuf system_config_path); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_find_xdg(GitBuf xdg_config_path); + + [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, 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, 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, - bool force); + 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, MarshalTypeRef = typeof(Utf8Marshaler))] string valueToParse); + [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, MarshalTypeRef = typeof(Utf8Marshaler))] string valueToParse); + [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, MarshalTypeRef = typeof(Utf8Marshaler))] string valueToParse); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string valueToParse); - [DllImport(libgit2)] - internal static extern int git_config_set_bool( - ConfigurationSafeHandle cfg, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string value); + [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); - // Ordinarily we would decorate the `url` parameter with the Utf8Marshaler like we do everywhere + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_iterator_glob_new( + out IntPtr iter, + IntPtr cfg, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string regexp); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_config_next( + out IntPtr entry, + IntPtr iter); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_config_iterator_free(IntPtr iter); + + [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 Utf8Marshaler.FromNative manually. See the discussion here: + // 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, IntPtr username_from_url, - uint allowed_types, + 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, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_cred_userpass_plaintext_new( out IntPtr cred, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (Utf8Marshaler))] string username, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (Utf8Marshaler))] string password); - - [DllImport(libgit2)] - internal static extern void git_diff_list_free(IntPtr diff); - - [DllImport(libgit2)] - internal static extern int git_diff_tree_to_tree( - out DiffListSafeHandle 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 DiffListSafeHandle 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( - DiffListSafeHandle onto, - DiffListSafeHandle 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 DiffListSafeHandle 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 DiffListSafeHandle 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( - 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( - GitDiffDelta delta, - GitDiffRange range, - IntPtr header, - UIntPtr headerLen, + [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_data_cb( - GitDiffDelta delta, - GitDiffRange range, - GitDiffLineOrigin lineOrigin, - IntPtr content, - UIntPtr contentLen, + [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_print_patch( - DiffListSafeHandle diff, - git_diff_data_cb printCallback, + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_diff_binary_cb( + [In] git_diff_delta* delta, + [In] GitDiffBinary binary, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_diff_blobs( - GitObjectSafeHandle oldBlob, - GitObjectSafeHandle newBlob, + [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, 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_data_cb lineCallback, + git_diff_line_cb lineCallback, IntPtr payload); - [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 unsafe int git_diff_find_similar( + git_diff* diff, + GitDiffFindOptions options); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_diff_num_deltas(git_diff* diff); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_diff_delta* git_diff_get_delta(git_diff* diff, UIntPtr idx); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_features(); + + #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. - [DllImport(libgit2)] - internal static extern int git_ignore_add_rule( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (Utf8Marshaler))] string rules); + // 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); - [DllImport(libgit2)] - internal static extern int git_ignore_clear_internal_rules(RepositorySafeHandle repo); + // 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); - [DllImport(libgit2)] - internal static extern int git_ignore_path_is_ignored( + // 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, 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_clear_internal_rules(git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_ignore_path_is_ignored( out int ignored, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path); - - [DllImport(libgit2)] - internal static extern int git_index_add_bypath( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath 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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] 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 int git_index_find( - out UIntPtr pos, - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path); - - [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, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string path); + + [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, 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 int git_index_open( - out IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath indexpath); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_index_name_entrycount(git_index* handle); - [DllImport(libgit2)] - internal static extern int git_index_remove( - IndexSafeHandle index, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path, - int stage); + [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, 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_read( + git_index* index, + [MarshalAs(UnmanagedType.Bool)] bool force); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_index_reuc_entry* git_index_reuc_get_byindex(git_index* 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_write(git_index* 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(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_write_tree(out GitOid treeOid, IndexSafeHandle index); + [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_merge_base( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_index_clear(git_index* index); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_many( + out GitOid mergeBase, + git_repository* repo, + int length, + [In] GitOid[] input_array); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_merge_base_octopus( out GitOid mergeBase, - RepositorySafeHandle repo, - GitObjectSafeHandle one, - GitObjectSafeHandle two); + git_repository* repo, + int length, + [In] GitOid[] input_array); + + [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, 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)] + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_annotated_commit_id( + git_annotated_commit* annotatedCommit); + + [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, 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, + git_repository* repo, + [In] IntPtr[] their_heads, + int their_heads_len); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_annotated_commit_free(git_annotated_commit* commit); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_message_prettify( - byte[] message_out, // NB: This is more properly a StringBuilder, but it's UTF8 - UIntPtr buffer_size, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, - bool strip_comments); + 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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string notes_ref, + 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, MarshalTypeRef = typeof(Utf8Marshaler))] string note, + [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)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_note_message(NoteSafeHandle note); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_note_message(git_note* note); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_note_oid(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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string notes_ref, + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string notes_ref, - SignatureSafeHandle author, - SignatureSafeHandle committer, + [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, + git_signature* author, + git_signature* committer, ref GitOid oid); - [DllImport(libgit2)] - internal static extern int git_note_default_ref( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] 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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string notes_ref, + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_odb_foreach( + git_odb* odb, + git_odb_foreach_cb cb, + IntPtr payload); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_object_free(git_object* 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 void git_odb_free(IntPtr odb); + [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 void git_object_free(IntPtr obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_odb_stream_free(git_odb_stream* stream); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_object_id(GitObjectSafeHandle obj); + [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 int git_object_lookup(out GitObjectSafeHandle obj, RepositorySafeHandle repo, ref GitOid id, GitObjectType type); + [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_peel( - out GitObjectSafeHandle peeled, - GitObjectSafeHandle obj, + [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, 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 GitObjectType git_object_type(GitObjectSafeHandle obj); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_object_short_id( + GitBuf buf, + git_object* obj); - [DllImport(libgit2)] - internal static extern int git_push_new(out PushSafeHandle push, RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_object_type(git_object* obj); - [DllImport(libgit2)] - internal static extern int git_push_add_refspec( - PushSafeHandle push, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string pushRefSpec); + [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_push_finish(PushSafeHandle push); + [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 void git_push_free(IntPtr push); + [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, + git_patch* patch); - [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 void git_patch_free(git_patch* patch); - internal delegate int push_status_foreach_cb( - IntPtr reference, - IntPtr msg, - IntPtr data); + /* 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_unpack_ok(PushSafeHandle push); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_packbuilder_free(git_packbuilder* packbuilder); - [DllImport(libgit2)] - internal static extern int git_push_update_tips(PushSafeHandle push); + [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)] - internal static extern int git_reference_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - ref GitOid oid, - [MarshalAs(UnmanagedType.Bool)] bool force); + [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_reference_symbolic_create( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string target, - [MarshalAs(UnmanagedType.Bool)] bool force); + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_insert_tree( + git_packbuilder* packbuilder, + ref GitOid id); - [DllImport(libgit2)] - internal static extern int git_reference_delete(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_packbuilder_new(out git_packbuilder* packbuilder, git_repository* repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_packbuilder_object_count(git_packbuilder* packbuilder); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_packbuilder_set_threads(git_packbuilder* packbuilder, uint numThreads); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_packbuilder_written(git_packbuilder* packbuilder); + + [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, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + + [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, + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string glob, - GitReferenceType flags, + [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, MarshalTypeRef = typeof(Utf8Marshaler))] string refname); - - [DllImport(libgit2)] - internal static extern int git_reference_lookup( - out ReferenceSafeHandle reference, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name); + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string refname); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reference_list(out GitStrArray array, git_repository* 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, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_reference_name(git_reference* reference); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_reference_target(git_reference* 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, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + + [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, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_reference_name(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, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_reference_target(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_reference_symbolic_target(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_rename( - ReferenceSafeHandle reference, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string newName, - [MarshalAs(UnmanagedType.Bool)] bool force); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitReferenceType git_reference_type(git_reference* reference); - [DllImport(libgit2)] - internal static extern int git_reference_resolve(out ReferenceSafeHandle resolvedReference, ReferenceSafeHandle reference); + [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 int git_reference_set_target(ReferenceSafeHandle reference, ref GitOid id); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_reflog_free(git_reflog* reflog); - [DllImport(libgit2)] - internal static extern int git_reference_symbolic_set_target( - ReferenceSafeHandle reference, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string target); + [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)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_reference_symbolic_target(ReferenceSafeHandle reference); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_reflog_entrycount + (git_reflog* reflog); - [DllImport(libgit2)] - internal static extern GitReferenceType git_reference_type(ReferenceSafeHandle reference); + [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 int git_refspec_rtransform( - byte[] target, - UIntPtr outlen, - GitFetchSpecHandle refSpec, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name); - - [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string url); - - [DllImport(libgit2)] - internal static extern void git_remote_disconnect(RemoteSafeHandle remote); - - [DllImport(libgit2)] - internal static extern int git_remote_download( - RemoteSafeHandle remote, - git_transfer_progress_callback progress_cb, - IntPtr payload); + [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 void git_remote_free(IntPtr remote); + [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 int git_remote_is_valid_name( - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string remote_name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_signature* git_reflog_entry_committer( + git_reflog_entry* entry); - [DllImport(libgit2)] - internal static extern int git_remote_load( - out RemoteSafeHandle remote, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_reflog_entry_message(git_reflog_entry* entry); - internal delegate int git_headlist_cb(ref GitRemoteHead remoteHeadPtr, IntPtr payload); + [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)] - internal static extern int git_remote_ls(RemoteSafeHandle remote, git_headlist_cb headlist_cb, IntPtr payload); - [DllImport(libgit2)] - internal static extern GitFetchSpecHandle git_remote_fetchspec(RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_refspec_rtransform( + GitBuf buf, + IntPtr refspec, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern string git_refspec_string( + IntPtr refSpec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern RefSpecDirection git_refspec_direction(IntPtr refSpec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern string git_refspec_dst( + IntPtr refSpec); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern string git_refspec_src( + 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, 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, 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, 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_fetch( + git_remote* remote, + ref GitStrArray refspecs, + GitFetchOptions fetch_opts, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string log_message); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_refspec* git_remote_get_refspec(git_remote* 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_remote_push( + git_remote* remote, + ref GitStrArray refSpecs, + GitPushOptions opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_remote_refspec_count(git_remote* remote); + + [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, 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, 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, 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)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_remote_name(RemoteSafeHandle remote); + [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_save(RemoteSafeHandle remote); + [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); - [DllImport(libgit2)] - internal static extern void git_remote_set_cred_acquire_cb( - RemoteSafeHandle remote, - git_cred_acquire_cb cred_acquire_cb, - IntPtr payload); + [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)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_remote_url(RemoteSafeHandle remote); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_remote_name(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_url(git_remote* remote); - [DllImport(libgit2)] - internal static extern int git_remote_set_callbacks( - RemoteSafeHandle remote, - ref GitRemoteCallbacks callbacks); + [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_fetchspec( - RemoteSafeHandle remote, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof (Utf8Marshaler))] string fetchrefspec); + [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); - internal delegate void remote_progress_callback(IntPtr str, int len, IntPtr data); + [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)] - internal static extern int git_remote_update_tips(RemoteSafeHandle remote); + [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)] + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] internal static extern int git_repository_discover( - byte[] repository_path, // NB: This is more properly a StringBuilder, but it's UTF8 - UIntPtr size, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath start_path, + GitBuf buf, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath start_path, [MarshalAs(UnmanagedType.Bool)] bool across_fs, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath ceiling_dirs); + [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, @@ -775,266 +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)] - internal static extern int git_repository_head_detached(RepositorySafeHandle 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_orphan(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_index(out IndexSafeHandle index, 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_init( - out RepositorySafeHandle repository, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path, - [MarshalAs(UnmanagedType.Bool)] bool isBare); + [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_is_bare(RepositorySafeHandle handle); + [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_empty(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_bare(IntPtr handle); - [DllImport(libgit2)] - internal static extern int git_repository_merge_cleanup(RepositorySafeHandle repo); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_repository_is_shallow(IntPtr 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( - byte[] message_out, - UIntPtr buffer_size, - RepositorySafeHandle repository); - - [DllImport(libgit2)] - internal static extern int git_repository_odb(out ObjectDatabaseSafeHandle odb, RepositorySafeHandle repo); - - [DllImport(libgit2)] - internal static extern int git_repository_open( - out RepositorySafeHandle repository, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath path); - - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_path(RepositorySafeHandle repository); - - [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)] - internal static extern int git_repository_set_workdir( - RepositorySafeHandle repository, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath workdir, - bool update_gitlink); - - [DllImport(libgit2)] - internal static extern int git_repository_state( - RepositorySafeHandle repository); - - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathNoCleanupMarshaler))] - internal static extern FilePath git_repository_workdir(RepositorySafeHandle repository); - - [DllImport(libgit2)] - internal static extern int git_reset( - RepositorySafeHandle repo, - GitObjectSafeHandle target, - ResetOptions reset_type); - - [DllImport(libgit2)] - internal static extern int git_revparse_single( - out GitObjectSafeHandle obj, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string spec); - - [DllImport(libgit2)] - internal static extern void git_revwalk_free(IntPtr walker); - - [DllImport(libgit2)] - internal static extern int git_revwalk_hide(RevWalkerSafeHandle walker, ref GitOid commit_id); - - [DllImport(libgit2)] - internal static extern int git_revwalk_new(out RevWalkerSafeHandle walker, RepositorySafeHandle repo); - - [DllImport(libgit2)] - internal static extern int git_revwalk_next(out GitOid id, RevWalkerSafeHandle walker); - - [DllImport(libgit2)] - internal static extern int git_revwalk_push(RevWalkerSafeHandle walker, ref GitOid id); - - [DllImport(libgit2)] - internal static extern void git_revwalk_reset(RevWalkerSafeHandle walker); - - [DllImport(libgit2)] - internal static extern void git_revwalk_sorting(RevWalkerSafeHandle walk, GitSortOptions sort); - - [DllImport(libgit2)] - internal static extern void git_signature_free(IntPtr signature); - - [DllImport(libgit2)] - internal static extern int git_signature_new( - out SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string email, + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_message( + GitBuf buf, + git_repository* repository); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_new( + out git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_odb(out git_odb* odb, git_repository* repo); + + [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, 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, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] + 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_index( + git_repository* repository, + git_index* index); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_repository_set_head_detached( + git_repository* repo, + ref GitOid commitish); + + [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, 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, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxFilePathNoCleanupMarshaler))] + internal static extern FilePath git_repository_workdir(IntPtr repository); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_reset( + git_repository* repo, + git_object* target, + ResetMode reset_type, + ref GitCheckoutOpts opts); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revert( + git_repository* repo, + git_object* commit, + GitRevertOpts opts); + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_revwalk_free(git_revwalk* walker); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_hide(git_revwalk* walker, ref GitOid commit_id); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_new(out git_revwalk* walker, git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_next(out GitOid id, git_revwalk* walker); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_push(git_revwalk* walker, ref GitOid id); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_reset(git_revwalk* walker); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_sorting(git_revwalk* walk, CommitSortStrategies sort); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_revwalk_simplify_first_parent(git_revwalk* walk); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_signature_free(git_signature* 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_stash_save( + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_save( out GitOid id, - RepositorySafeHandle repo, - SignatureSafeHandle stasher, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, - StashOptions flags); + 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_status_file( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_stash_drop(git_repository* repo, UIntPtr index); + + [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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath filepath); + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictFilePathMarshaler))] FilePath filepath); + + + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_status_list_entrycount( + git_status_list* statusList); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_status_entry* git_status_byindex( + git_status_list* list, + UIntPtr idx); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_status_list_free( + git_status_list* statusList); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern void git_strarray_free( + ref GitStrArray array); + + [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); - internal delegate int git_status_cb( - IntPtr path, - uint statusflags, + [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_status_foreach(RepositorySafeHandle repo, git_status_cb cb, IntPtr payload); + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_submodule_free(git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_submodule_path( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_submodule_url( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_index_id( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_head_id( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_submodule_wd_id( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleIgnore git_submodule_ignore( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleUpdate git_submodule_update_strategy( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules( + git_submodule* submodule); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_reload( + git_submodule* submodule, + [MarshalAs(UnmanagedType.Bool)] bool force); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_submodule_status( + out SubmoduleStatus status, + 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_create( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_annotation_create( out GitOid oid, - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - GitObjectSafeHandle target, - SignatureSafeHandle signature, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + git_object* target, + git_signature* signature, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string message); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_create( + out GitOid oid, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + 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, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, - GitObjectSafeHandle target, + git_repository* repo, + [MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(StrictUtf8Marshaler))] string name, + git_object* target, [MarshalAs(UnmanagedType.Bool)] bool force); - [DllImport(libgit2)] - internal static extern int git_tag_delete( - RepositorySafeHandle repo, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string tagName); + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_tag_list(out GitStrArray array, git_repository* repo); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_tag_message(git_object* tag); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + 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, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tag_target_id(git_object* tag); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tag_target_type(git_object* tag); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_init(); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_libgit2_shutdown(); - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_tag_message(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_openssl_set_locking(); - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_tag_name(GitObjectSafeHandle tag); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate void git_trace_cb(LogLevel level, IntPtr message); - [DllImport(libgit2)] - internal static extern IntPtr git_tag_tagger(GitObjectSafeHandle tag); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_trace_set(LogLevel level, git_trace_cb trace_cb); - [DllImport(libgit2)] - internal static extern OidSafeHandle git_tag_target_id(GitObjectSafeHandle tag); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload); - [DllImport(libgit2)] - internal static extern GitObjectType git_tag_target_type(GitObjectSafeHandle tag); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate int git_transport_cb(out IntPtr transport, IntPtr remote, IntPtr payload); - [DllImport(libgit2)] - internal static extern int git_threads_init(); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal unsafe delegate int git_transport_certificate_check_cb(git_certificate* cert, int valid, IntPtr hostname, IntPtr payload); - [DllImport(libgit2)] - internal static extern void git_threads_shutdown(); + [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, CallingConvention = CallingConvention.Cdecl)] + internal static extern int git_transport_smart( + out IntPtr transport, + IntPtr remote, + IntPtr definition); + + [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); - internal delegate void git_transfer_progress_callback(ref GitTransferProgress stats, IntPtr payload); + [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)] - internal static extern uint git_tree_entry_filemode(SafeHandle entry); + [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 TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, UIntPtr idx); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe uint git_tree_entry_filemode(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern int git_tree_entry_bypath( - out TreeEntrySafeHandle_Owned tree, - GitObjectSafeHandle root, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(FilePathMarshaler))] FilePath treeentry_path); + [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 void git_tree_entry_free(IntPtr treeEntry); + [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 OidSafeHandle git_tree_entry_id(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe void git_tree_entry_free(git_tree_entry* treeEntry); - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] - internal static extern string git_tree_entry_name(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe git_oid* git_tree_entry_id(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern GitObjectType git_tree_entry_type(SafeHandle entry); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + [return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] + internal static extern unsafe string git_tree_entry_name(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern UIntPtr git_tree_entrycount(GitObjectSafeHandle tree); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry); - [DllImport(libgit2)] - internal static extern int git_treebuilder_create(out TreeBuilderSafeHandle builder, IntPtr src); + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe UIntPtr git_tree_entrycount(git_object* tree); - [DllImport(libgit2)] - internal static extern int git_treebuilder_insert( + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_new(out git_treebuilder* builder, git_repository* repo, IntPtr src); + + [DllImport(libgit2, CallingConvention = CallingConvention.Cdecl)] + internal static extern unsafe int git_treebuilder_insert( IntPtr entry_out, - TreeBuilderSafeHandle builder, - [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string treeentry_name, + 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, 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)] - internal static extern int git_blob_is_binary(GitObjectSafeHandle blob); + [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, 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/PackbuilderCallbacks.cs b/LibGit2Sharp/Core/PackbuilderCallbacks.cs new file mode 100644 index 000000000..7c496654c --- /dev/null +++ b/LibGit2Sharp/Core/PackbuilderCallbacks.cs @@ -0,0 +1,38 @@ +using System; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp.Core +{ + internal class PackbuilderCallbacks + { + private readonly PackBuilderProgressHandler onPackBuilderProgress; + + /// S + /// Constructor to set up the native callback given managed delegate. + /// + /// The delegate that the git_packbuilder_progress will call. + internal PackbuilderCallbacks(PackBuilderProgressHandler onPackBuilderProgress) + { + this.onPackBuilderProgress = onPackBuilderProgress; + } + + /// + /// Generates a delegate that matches the native git_packbuilder_progress function's signature and wraps the delegate. + /// + /// A delegate method with a signature that matches git_transfer_progress_callback. + internal NativeMethods.git_packbuilder_progress GenerateCallback() + { + if (onPackBuilderProgress == null) + { + return null; + } + + return new PackbuilderCallbacks(onPackBuilderProgress).OnGitPackBuilderProgress; + } + + private int OnGitPackBuilderProgress(int stage, uint current, uint total, IntPtr payload) + { + return Proxy.ConvertResultToCancelFlag(onPackBuilderProgress((PackBuilderStage)stage, (int)current, (int)total)); + } + } +} diff --git a/LibGit2Sharp/Core/PathCase.cs b/LibGit2Sharp/Core/PathCase.cs new file mode 100644 index 000000000..600f693de --- /dev/null +++ b/LibGit2Sharp/Core/PathCase.cs @@ -0,0 +1,37 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal class PathCase + { + private readonly StringComparer comparer; + private readonly StringComparison comparison; + + public PathCase(IRepository repo) + { + var value = repo.Config.Get("core.ignorecase"); + switch (value != null && value.Value) + { + case true: + comparer = StringComparer.OrdinalIgnoreCase; + comparison = StringComparison.OrdinalIgnoreCase; + break; + + default: + comparer = StringComparer.Ordinal; + comparison = StringComparison.Ordinal; + break; + } + } + + public StringComparer Comparer + { + get { return comparer; } + } + + public bool StartsWith(string path, string value) + { + return path != null && path.StartsWith(value, comparison); + } + } +} 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 89e53cb80..83d35e22c 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -1,11 +1,11 @@ -using System; +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 LibGit2Sharp.Core.Compat; +using System.Text; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; @@ -14,86 +14,90 @@ namespace LibGit2Sharp.Core { internal class Proxy { - #region giterr_ + internal static readonly bool isOSXArm64 = RuntimeInformation.ProcessArchitecture == Architecture.Arm64 + && RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - public static void giterr_set_str(GitErrorCategory error_class, Exception exception) + #region git_blame_ + + public static unsafe BlameHandle git_blame_file( + RepositoryHandle repo, + string path, + git_blame_options options) { - if (exception is OutOfMemoryException) - { - NativeMethods.giterr_set_oom(); - } - else - { - NativeMethods.giterr_set_str(error_class, exception.Message); - } + 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 giterr_set_str(GitErrorCategory error_class, String errorString) + public static unsafe git_blame_hunk* git_blame_get_hunk_byindex(BlameHandle blame, uint idx) { - NativeMethods.giterr_set_str(error_class, errorString); + 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); - Ensure.ZeroResult(res); + IntPtr writestream_ptr; - return oid; - } + Ensure.ZeroResult(NativeMethods.git_blob_create_from_stream(out writestream_ptr, repo, hintpath)); + return writestream_ptr; } - public static ObjectId git_blob_create_fromdisk(RepositorySafeHandle repo, FilePath path) + public static unsafe ObjectId git_blob_create_fromstream_commit(IntPtr writestream_ptr) { - using (ThreadAffinity()) - { - var oid = new GitOid(); - int res = NativeMethods.git_blob_create_fromdisk(ref oid, repo, path); - Ensure.ZeroResult(res); + var oid = new GitOid(); + Ensure.ZeroResult(NativeMethods.git_blob_create_from_stream_commit(ref oid, writestream_ptr)); + return oid; + } - return oid; - } + public static unsafe ObjectId git_blob_create_from_disk(RepositoryHandle repo, FilePath path) + { + var oid = new GitOid(); + int res = NativeMethods.git_blob_create_from_disk(ref oid, repo, path); + Ensure.ZeroResult(res); + + 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 byte[] git_blob_rawcontent(RepositorySafeHandle repo, ObjectId id, int size) + public static unsafe UnmanagedMemoryStream git_blob_filtered_content_stream(RepositoryHandle repo, ObjectId id, string path, bool check_for_binary_data) { - using (var obj = new ObjectSafeWrapper(id, repo)) + var buf = new GitBuf(); + var handle = new ObjectSafeWrapper(id, repo, throwIfMissing: true).ObjectPtr; + + return new RawContentStream(handle, h => { - var arr = new byte[size]; - Marshal.Copy(NativeMethods.git_blob_rawcontent(obj.ObjectPtr), arr, 0, size); - return arr; - } + Ensure.ZeroResult(NativeMethods.git_blob_filtered_content(buf, h, path, check_for_binary_data)); + return buf.ptr; + }, + h => (long)buf.size, + 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) { - return new RawContentStream(id, repo, NativeMethods.git_blob_rawcontent, 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); @@ -105,95 +109,121 @@ 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) + 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)) + 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); + 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 unsafe void git_branch_delete(ReferenceHandle reference) + { + int res = NativeMethods.git_branch_delete(reference); + Ensure.ZeroResult(res); } - public static void git_branch_delete(ReferenceSafeHandle reference) + public static IEnumerable git_branch_iterator(Repository repo, GitBranchType branchType) { - using (ThreadAffinity()) + IntPtr iter; + var res = NativeMethods.git_branch_iterator_new(out iter, repo.Handle.AsIntPtr(), branchType); + Ensure.ZeroResult(res); + + try { - int res = NativeMethods.git_branch_delete(reference); - reference.SetHandleAsInvalid(); - Ensure.ZeroResult(res); + while (true) + { + IntPtr refPtr = IntPtr.Zero; + GitBranchType _branchType; + res = NativeMethods.git_branch_next(out refPtr, out _branchType, iter); + if (res == (int)GitErrorCode.IterOver) + { + yield break; + } + Ensure.ZeroResult(res); + + Reference reference; + using (var refHandle = new ReferenceHandle(refPtr, true)) + { + reference = Reference.BuildFromPtr(refHandle, repo); + } + yield return new Branch(repo, reference, reference.CanonicalName); + } + } + finally + { + NativeMethods.git_branch_iterator_free(iter); } } - public static ICollection git_branch_foreach( - RepositorySafeHandle repo, - GitBranchType branch_type, - Func resultSelector) + public static void git_branch_iterator_free(IntPtr iter) { - return git_foreach(resultSelector, c => NativeMethods.git_branch_foreach(repo, branch_type, (x, y, p) => c(x, y, p), IntPtr.Zero)); + NativeMethods.git_branch_iterator_free(iter); } - public static void git_branch_move(ReferenceSafeHandle reference, string new_branch_name, bool force) + public static unsafe ReferenceHandle git_branch_move(ReferenceHandle reference, string new_branch_name, bool force) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_branch_move(reference, new_branch_name, force); - Ensure.ZeroResult(res); - } + 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, ReferenceSafeHandle branch) + public static unsafe string git_branch_remote_name(RepositoryHandle repo, string canonical_branch_name, bool shouldThrowIfNotFound) { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - int bufSize = NativeMethods.git_branch_remote_name(null, UIntPtr.Zero, repo, branch); - Ensure.Int32Result(bufSize); + int res = NativeMethods.git_branch_remote_name(buf, repo, canonical_branch_name); - var buffer = new byte[bufSize]; - - int res = NativeMethods.git_branch_remote_name(buffer, (UIntPtr)buffer.Length, repo, branch); - Ensure.Int32Result(res); + if (!shouldThrowIfNotFound && + (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous)) + { + return null; + } - return Utf8Marshaler.Utf8FromBuffer(buffer) ?? string.Empty; + Ensure.ZeroResult(res); + return LaxUtf8Marshaler.FromNative(buf.ptr); } } - public static string git_branch_tracking_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 bufSize = NativeMethods.git_branch_tracking_name( - null, UIntPtr.Zero, handle, canonicalReferenceName); - - if (bufSize == (int)GitErrorCode.NotFound) + int res = NativeMethods.git_branch_upstream_name(buf, handle, canonicalReferenceName); + if (res == (int)GitErrorCode.NotFound) { return null; } - Ensure.Int32Result(bufSize); + Ensure.ZeroResult(res); + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } - var buffer = new byte[bufSize]; + #endregion - int res = NativeMethods.git_branch_tracking_name( - buffer, (UIntPtr)buffer.Length, handle, canonicalReferenceName); - Ensure.Int32Result(res); + #region git_buf_ - return Utf8Marshaler.Utf8FromBuffer(buffer); - } + public static void git_buf_dispose(GitBuf 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); @@ -201,90 +231,179 @@ 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) + { + int res = NativeMethods.git_checkout_index(repo, treeish, ref opts); + Ensure.ZeroResult(res); + } + + #endregion + + #region git_cherry_pick_ + + 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_checkout_index(repo, treeish, ref opts); + 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, - GitCloneOptions opts) + ref GitCloneOptions opts) { - using (ThreadAffinity()) - { - RepositorySafeHandle repo; - int res = NativeMethods.git_clone(out repo, url, workdir, 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, - string prettifiedMessage, + string message, Tree tree, - IEnumerable parentIds) + GitOid[] parentIds) { - using (ThreadAffinity()) - using (var treePtr = new ObjectSafeWrapper(tree.Id, repo)) - using (var parentObjectPtrs = new DisposableEnumerable(parentIds.Select(id => new ObjectSafeWrapper(id, repo)).ToList())) - 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; - string encoding = null; //TODO: Handle the encoding of the commit to be created - IntPtr[] parentsPtrs = parentObjectPtrs.Select(o => o.ObjectPtr.DangerousGetHandle()).ToArray(); - int res = NativeMethods.git_commit_create(out commitOid, repo, referenceName, authorHandle, - committerHandle, encoding, prettifiedMessage, treePtr.ObjectPtr, parentObjectPtrs.Count, parentsPtrs); + 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()); + Ensure.ZeroResult(res); return commitOid; } } - 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_message_encoding(GitObjectSafeHandle obj) + public static unsafe string git_commit_summary(ObjectHandle obj) + { + return NativeMethods.git_commit_summary(obj); + } + + public static unsafe string git_commit_message_encoding(ObjectHandle obj) { return NativeMethods.git_commit_message_encoding(obj); } - public static ObjectId git_commit_parent_oid(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)) { @@ -292,1713 +411,3327 @@ 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_oid(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 string git_config_find_global() + public static FilePath git_config_find_global() { return ConvertPath(NativeMethods.git_config_find_global); } - public static string git_config_find_system() + public static FilePath git_config_find_system() { return ConvertPath(NativeMethods.git_config_find_system); } - public static string git_config_find_xdg() + 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(Utf8Marshaler.FromNative(entry.namePtr), - (T)configurationParser[typeof(T)](Utf8Marshaler.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); } - public static ICollection git_config_foreach( - ConfigurationSafeHandle config, - Func resultSelector) + static readonly string non_existing_regex = Guid.NewGuid().ToString(); + + public static unsafe void git_config_add_string(ConfigurationHandle config, string name, string value) { - return git_foreach(resultSelector, c => NativeMethods.git_config_foreach(config, (e, p) => c(e, p), IntPtr.Zero)); + int res = NativeMethods.git_config_set_multivar(config, name, non_existing_regex, value); + Ensure.ZeroResult(res); } - #endregion - - #region git_diff_ - - public static void git_diff_blobs( - RepositorySafeHandle repo, - ObjectId oldBlob, - ObjectId newBlob, - GitDiffOptions options, - NativeMethods.git_diff_file_cb fileCallback, - NativeMethods.git_diff_hunk_cb hunkCallback, - NativeMethods.git_diff_data_cb lineCallback) + public static unsafe ICollection git_config_foreach( + ConfigurationHandle config, + Func resultSelector) { - 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, osw2.ObjectPtr, options, fileCallback, hunkCallback, lineCallback, IntPtr.Zero); - Ensure.ZeroResult(res); - } + return git_foreach(resultSelector, c => NativeMethods.git_config_foreach(config, (e, p) => c(e, p), IntPtr.Zero)); } - public static DiffListSafeHandle git_diff_tree_to_index( - RepositorySafeHandle repo, - IndexSafeHandle index, - ObjectId oldTree, - GitDiffOptions options) + public static IEnumerable> git_config_iterator_glob( + ConfigurationHandle config, + string regexp) { - using (ThreadAffinity()) - using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) + IntPtr iter; + var res = NativeMethods.git_config_iterator_glob_new(out iter, config.AsIntPtr(), regexp); + Ensure.ZeroResult(res); + try { - DiffListSafeHandle diff; - int res = NativeMethods.git_diff_tree_to_index(out diff, repo, osw.ObjectPtr, index, options); - Ensure.ZeroResult(res); + while (true) + { + IntPtr entry; + res = NativeMethods.git_config_next(out entry, iter); + if (res == (int)GitErrorCode.IterOver) + { + yield break; + } + Ensure.ZeroResult(res); - return diff; + yield return Configuration.BuildConfigEntry(entry); + } + } + finally + { + NativeMethods.git_config_iterator_free(iter); } } - public static void git_diff_list_free(IntPtr diff) + public static unsafe ConfigurationHandle git_config_snapshot(ConfigurationHandle config) { - NativeMethods.git_diff_list_free(diff); + git_config* handle; + int res = NativeMethods.git_config_snapshot(out handle, config); + Ensure.ZeroResult(res); + + return new ConfigurationHandle(handle, true); } - public static void git_diff_merge(DiffListSafeHandle onto, DiffListSafeHandle from) + public static unsafe IntPtr git_config_lock(git_config* config) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_diff_merge(onto, from); - Ensure.ZeroResult(res); - } + IntPtr txn; + int res = NativeMethods.git_config_lock(out txn, config); + Ensure.ZeroResult(res); + + return txn; } - public static void git_diff_print_patch(DiffListSafeHandle diff, NativeMethods.git_diff_data_cb printCallback) + #endregion + + #region git_cred_ + + public static void git_cred_free(IntPtr cred) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_diff_print_patch(diff, printCallback, IntPtr.Zero); - Ensure.ZeroResult(res); - } + NativeMethods.git_cred_free(cred); } - public static DiffListSafeHandle git_diff_tree_to_tree( - RepositorySafeHandle 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)) - { - DiffListSafeHandle diff; - int res = NativeMethods.git_diff_tree_to_tree(out diff, repo, osw1.ObjectPtr, osw2.ObjectPtr, options); - Ensure.ZeroResult(res); + #endregion - return diff; - } - } + #region git_describe_ - public static DiffListSafeHandle git_diff_index_to_workdir( - RepositorySafeHandle repo, - IndexSafeHandle index, - GitDiffOptions options) + public static unsafe string git_describe_commit( + RepositoryHandle repo, + ObjectId committishId, + DescribeOptions options) { - using (ThreadAffinity()) - { - DiffListSafeHandle diff; - int res = NativeMethods.git_diff_index_to_workdir(out diff, repo, index, options); - Ensure.ZeroResult(res); + Ensure.ArgumentPositiveInt32(options.MinimumCommitIdAbbreviatedSize, "options.MinimumCommitIdAbbreviatedSize"); - return diff; - } + using (var osw = new ObjectSafeWrapper(committishId, repo)) + { + 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, + }; + + 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(); + } + } + } + } + + #endregion + + #region git_diff_ + + public static unsafe void git_diff_blobs( + RepositoryHandle repo, + ObjectId oldBlob, + ObjectId newBlob, + GitDiffOptions options, + NativeMethods.git_diff_file_cb fileCallback, + NativeMethods.git_diff_hunk_cb hunkCallback, + NativeMethods.git_diff_line_cb lineCallback) + { + 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, + null, + hunkCallback, + lineCallback, + IntPtr.Zero); + + Ensure.ZeroResult(res); + } + } + + 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) + { + int res = NativeMethods.git_diff_foreach(diff, fileCallback, null, hunkCallback, lineCallback, IntPtr.Zero); + Ensure.ZeroResult(res); + } + + public static unsafe DiffHandle git_diff_tree_to_index( + RepositoryHandle repo, + IndexHandle index, + ObjectId oldTree, + GitDiffOptions options) + { + using (var osw = new ObjectSafeWrapper(oldTree, repo, true)) + { + git_diff* diff; + int res = NativeMethods.git_diff_tree_to_index(out diff, repo, osw.ObjectPtr, index, options); + Ensure.ZeroResult(res); + + return new DiffHandle(diff, true); + } + } + + public static unsafe void git_diff_merge(DiffHandle onto, DiffHandle from) + { + int res = NativeMethods.git_diff_merge(onto, from); + Ensure.ZeroResult(res); + } + + public static unsafe DiffHandle git_diff_tree_to_tree( + RepositoryHandle repo, + ObjectId oldTree, + ObjectId newTree, + GitDiffOptions options) + { + using (var osw1 = new ObjectSafeWrapper(oldTree, repo, true, throwIfMissing: true)) + using (var osw2 = new ObjectSafeWrapper(newTree, repo, true, throwIfMissing: true)) + { + git_diff* diff; + int res = NativeMethods.git_diff_tree_to_tree(out diff, repo, osw1.ObjectPtr, osw2.ObjectPtr, options); + Ensure.ZeroResult(res); + + return new DiffHandle(diff, true); + } + } + + public static unsafe DiffHandle git_diff_index_to_workdir( + RepositoryHandle repo, + IndexHandle index, + GitDiffOptions options) + { + git_diff* diff; + int res = NativeMethods.git_diff_index_to_workdir(out diff, repo, index, options); + Ensure.ZeroResult(res); + + return new DiffHandle(diff, true); } - public static DiffListSafeHandle 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)) { - DiffListSafeHandle 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 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_graph_ + #region git_error_ + + public static int git_error_set_str(GitErrorCategory error_class, Exception exception) + { + if (exception is OutOfMemoryException) + { + NativeMethods.git_error_set_oom(); + return 0; + } + else + { + return NativeMethods.git_error_set_str(error_class, ErrorMessageFromException(exception)); + } + } + + public static int git_error_set_str(GitErrorCategory error_class, string errorString) + { + 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(); + } - public static Tuple git_graph_ahead_behind(RepositorySafeHandle repo, ObjectId firstId, ObjectId secondId) + private static void BuildErrorMessageFromException(StringBuilder sb, int level, Exception ex) { - GitOid oid1 = firstId.Oid; - GitOid oid2 = secondId.Oid; + string indent = new string(' ', level * 4); + sb.AppendFormat("{0}{1}", indent, ex.Message); - using (ThreadAffinity()) + if (ex is AggregateException) { - UIntPtr ahead; - UIntPtr behind; + AggregateException aggregateException = ((AggregateException)ex).Flatten(); - int res = NativeMethods.git_graph_ahead_behind(out ahead, out behind, repo, ref oid1, ref oid2); + if (aggregateException.InnerExceptions.Count == 1) + { + sb.AppendLine(); + sb.AppendLine(); - Ensure.ZeroResult(res); + 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 unsafe FilterMode git_filter_source_mode(git_filter_source* filterSource) + { + var res = NativeMethods.git_filter_source_mode(filterSource); + return (FilterMode)res; + } + + #endregion - return new Tuple((int)ahead, (int)behind); + #region git_graph_ + + public static unsafe Tuple git_graph_ahead_behind(RepositoryHandle repo, Commit first, Commit second) + { + if (first == null || second == null) + { + return new Tuple(null, null); } + + GitOid oid1 = first.Id.Oid; + GitOid oid2 = second.Id.Oid; + + UIntPtr ahead; + UIntPtr behind; + + int res = NativeMethods.git_graph_ahead_behind(out ahead, out behind, repo, ref oid1, ref oid2); + + Ensure.ZeroResult(res); + + return new Tuple((int)ahead, (int)behind); + } + + 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); + + Ensure.BooleanResult(res); + + 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) + { + int res = NativeMethods.git_ignore_add_rule(repo, rules); + Ensure.ZeroResult(res); + } + + public static unsafe void git_ignore_clear_internal_rules(RepositoryHandle repo) + { + int res = NativeMethods.git_ignore_clear_internal_rules(repo); + Ensure.ZeroResult(res); + } + + public static unsafe bool git_ignore_path_is_ignored(RepositoryHandle repo, string path) + { + int ignored; + int res = NativeMethods.git_ignore_path_is_ignored(out ignored, repo, path); + Ensure.ZeroResult(res); + + return (ignored != 0); + } + + #endregion + + #region git_index_ + + public static unsafe void git_index_add(IndexHandle index, git_index_entry* entry) + { + int res = NativeMethods.git_index_add(index, entry); + Ensure.ZeroResult(res); + } + + public static unsafe void git_index_add_bypath(IndexHandle index, FilePath path) + { + int res = NativeMethods.git_index_add_bypath(index, path); + Ensure.ZeroResult(res); + } + + public static unsafe Conflict git_index_conflict_get( + IndexHandle index, + string path) { - using (ThreadAffinity()) + git_index_entry* ancestor, ours, theirs; + + int res = NativeMethods.git_index_conflict_get(out ancestor, + out ours, + out theirs, + index, + path); + + if (res == (int)GitErrorCode.NotFound) { - int res = NativeMethods.git_ignore_add_rule(repo, rules); - Ensure.ZeroResult(res); + return null; + } + + Ensure.ZeroResult(res); + + 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 unsafe Conflict git_index_conflict_next(ConflictIteratorHandle iterator) + { + 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) + { + return null; } + + Ensure.ZeroResult(res); + + return new Conflict(IndexEntry.BuildFromPtr(ancestor), + IndexEntry.BuildFromPtr(ours), + IndexEntry.BuildFromPtr(theirs)); + } + + public static unsafe int git_index_entrycount(IndexHandle index) + { + return NativeMethods.git_index_entrycount(index) + .ConvertToInt(); + } + + public static unsafe StageLevel git_index_entry_stage(git_index_entry* entry) + { + return (StageLevel)NativeMethods.git_index_entry_stage(entry); + } + + public static unsafe git_index_entry* git_index_get_byindex(IndexHandle index, UIntPtr n) + { + return NativeMethods.git_index_get_byindex(index, n); + } + + public static unsafe git_index_entry* git_index_get_bypath(IndexHandle index, string path, int stage) + { + return NativeMethods.git_index_get_bypath(index, path, stage); + } + + public static unsafe bool git_index_has_conflicts(IndexHandle index) + { + int res = NativeMethods.git_index_has_conflicts(index); + Ensure.BooleanResult(res); + + return res != 0; + } + + public static unsafe int git_index_name_entrycount(IndexHandle index) + { + return NativeMethods.git_index_name_entrycount(index) + .ConvertToInt(); + } + + 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 unsafe IndexHandle git_index_open(FilePath indexpath) + { + git_index* handle; + int res = NativeMethods.git_index_open(out handle, indexpath); + Ensure.ZeroResult(res); + + return new IndexHandle(handle, true); + } + + public static unsafe void git_index_read(IndexHandle index) + { + int res = NativeMethods.git_index_read(index, false); + Ensure.ZeroResult(res); + } + + public static unsafe void git_index_remove_bypath(IndexHandle index, string path) + { + int res = NativeMethods.git_index_remove_bypath(index, path); + Ensure.ZeroResult(res); + } + + public static unsafe int git_index_reuc_entrycount(IndexHandle index) + { + return NativeMethods.git_index_reuc_entrycount(index) + .ConvertToInt(); + } + + 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 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 unsafe void git_index_write(IndexHandle index) + { + int res = NativeMethods.git_index_write(index); + Ensure.ZeroResult(res); + } + + public static unsafe ObjectId git_index_write_tree(IndexHandle index) + { + GitOid treeOid; + int res = NativeMethods.git_index_write_tree(out treeOid, index); + Ensure.ZeroResult(res); + + return treeOid; + } + + public static unsafe ObjectId git_index_write_tree_to(IndexHandle index, RepositoryHandle repo) + { + GitOid treeOid; + int res = NativeMethods.git_index_write_tree_to(out treeOid, index, repo); + Ensure.ZeroResult(res); + + return treeOid; + } + + public static unsafe void git_index_read_fromtree(Index index, ObjectHandle tree) + { + 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 unsafe IndexHandle git_merge_commits(RepositoryHandle repo, ObjectHandle ourCommit, ObjectHandle theirCommit, GitMergeOpts opts, out bool earlyStop) + { + git_index* index; + int res = NativeMethods.git_merge_commits(out index, repo, ourCommit, theirCommit, ref opts); + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + + return new IndexHandle(index, true); + } + + 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); + + if (res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(res); + + return ret; + } + + public static unsafe ObjectId git_merge_base_octopus(RepositoryHandle repo, GitOid[] commitIds) + { + GitOid ret; + int res = NativeMethods.git_merge_base_octopus(out ret, repo, commitIds.Length, commitIds); + + if (res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(res); + + 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 unsafe AnnotatedCommitHandle git_annotated_commit_lookup(RepositoryHandle repo, GitOid oid) + { + git_annotated_commit* commit; + + int res = NativeMethods.git_annotated_commit_lookup(out commit, repo, ref oid); + + Ensure.ZeroResult(res); + + return new AnnotatedCommitHandle(commit, true); + } + + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_ref(RepositoryHandle repo, ReferenceHandle reference) + { + git_annotated_commit* commit; + + int res = NativeMethods.git_annotated_commit_from_ref(out commit, repo, reference); + + Ensure.ZeroResult(res); + + return new AnnotatedCommitHandle(commit, true); + } + + public static unsafe AnnotatedCommitHandle git_annotated_commit_from_revspec(RepositoryHandle repo, string revspec) + { + git_annotated_commit* commit; + + int res = NativeMethods.git_annotated_commit_from_revspec(out commit, repo, revspec); + + Ensure.ZeroResult(res); + + return new AnnotatedCommitHandle(commit, true); + } + + public static unsafe ObjectId git_annotated_commit_id(AnnotatedCommitHandle mergeHead) + { + return ObjectId.BuildFromPtr(NativeMethods.git_annotated_commit_id(mergeHead)); + } + + public static unsafe void git_merge(RepositoryHandle repo, AnnotatedCommitHandle[] heads, GitMergeOpts mergeOptions, GitCheckoutOpts checkoutOptions, out bool earlyStop) + { + 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); + + if (res == (int)GitErrorCode.MergeConflict) + { + earlyStop = true; + } + else + { + earlyStop = false; + Ensure.ZeroResult(res); + } + } + + public static unsafe void git_merge_analysis( + RepositoryHandle repo, + AnnotatedCommitHandle[] heads, + out GitMergeAnalysis analysis_out, + out GitMergePreference preference_out) + { + 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); + } + + #endregion + + #region git_message_ + + public static string git_message_prettify(string message, char? commentChar) + { + if (string.IsNullOrEmpty(message)) + { + return string.Empty; + } + + int comment = commentChar.GetValueOrDefault(); + if (comment > sbyte.MaxValue) + { + throw new InvalidOperationException("Only single byte characters are allowed as commentary characters in a message (eg. '#')."); + } + + using (var buf = new GitBuf()) + { + int res = NativeMethods.git_message_prettify(buf, message, true, (sbyte)comment); + Ensure.Int32Result(res); + + return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + } + + #endregion + + #region git_note_ + + public static unsafe ObjectId git_note_create( + RepositoryHandle repo, + string notes_ref, + Signature author, + Signature committer, + ObjectId targetId, + string note, + bool force) + { + 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, notes_ref, authorHandle, committerHandle, ref oid, note, force ? 1 : 0); + Ensure.ZeroResult(res); + + return noteOid; + } + } + + public static unsafe string git_note_default_ref(RepositoryHandle repo) + { + using (var buf = new GitBuf()) + { + int res = NativeMethods.git_note_default_ref(buf, repo); + Ensure.ZeroResult(res); + + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + + 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), GitErrorCode.NotFound); + } + + public static unsafe string git_note_message(NoteHandle note) + { + return NativeMethods.git_note_message(note); + } + + public static unsafe ObjectId git_note_id(NoteHandle note) + { + return ObjectId.BuildFromPtr(NativeMethods.git_note_id(note)); + } + + public static unsafe NoteHandle git_note_read(RepositoryHandle repo, string notes_ref, ObjectId id) + { + GitOid oid = id.Oid; + git_note* note; + + int res = NativeMethods.git_note_read(out note, repo, notes_ref, ref oid); + + if (res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(res); + + return new NoteHandle(note, true); + } + + public static unsafe void git_note_remove(RepositoryHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) + { + using (SignatureHandle authorHandle = author.BuildHandle()) + using (SignatureHandle committerHandle = committer.BuildHandle()) + { + GitOid oid = targetId.Oid; + + int res = NativeMethods.git_note_remove(repo, notes_ref, authorHandle, committerHandle, ref oid); + + if (res == (int)GitErrorCode.NotFound) + { + return; + } + + Ensure.ZeroResult(res); + } + } + + #endregion + + #region git_object_ + + public static unsafe ObjectId git_object_id(ObjectHandle obj) + { + return ObjectId.BuildFromPtr(NativeMethods.git_object_id(obj)); + } + + public static unsafe ObjectHandle git_object_lookup(RepositoryHandle repo, ObjectId id, GitObjectType type) + { + git_object* 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; + } + + return new ObjectHandle(handle, true); + } + + public static unsafe ObjectHandle git_object_peel(RepositoryHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) + { + git_object* peeled; + int res; + + using (var obj = new ObjectSafeWrapper(id, repo)) + { + res = NativeMethods.git_object_peel(out peeled, obj.ObjectPtr, type); + } + + 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 unsafe string git_object_short_id(RepositoryHandle repo, ObjectId id) + { + using (var obj = new ObjectSafeWrapper(id, repo)) + using (var buf = new GitBuf()) + { + int res = NativeMethods.git_object_short_id(buf, obj.ObjectPtr); + Ensure.Int32Result(res); + + return LaxUtf8Marshaler.FromNative(buf.ptr); + } + } + + public static unsafe GitObjectType git_object_type(ObjectHandle obj) + { + return NativeMethods.git_object_type(obj); + } + + #endregion + + #region git_odb_ + + public static unsafe void git_odb_add_backend(ObjectDatabaseHandle odb, IntPtr backend, int priority) + { + Ensure.ZeroResult(NativeMethods.git_odb_add_backend(odb, backend, priority)); + } + + public static IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len) + { + IntPtr toReturn = NativeMethods.git_odb_backend_malloc(backend, len); + + if (IntPtr.Zero == toReturn) + { + throw new LibGit2SharpException("Unable to allocate {0} bytes; out of memory", + len, + GitErrorCode.Error, + GitErrorCategory.NoMemory); + } + + return toReturn; + } + + public static unsafe bool git_odb_exists(ObjectDatabaseHandle odb, ObjectId id) + { + GitOid oid = id.Oid; + + int res = NativeMethods.git_odb_exists(odb, ref oid); + Ensure.BooleanResult(res); + + return (res == 1); + } + + public static unsafe GitObjectMetadata git_odb_read_header(ObjectDatabaseHandle odb, ObjectId id) + { + GitOid oid = id.Oid; + UIntPtr length; + GitObjectType objectType; + + int res = NativeMethods.git_odb_read_header(out length, out objectType, odb, ref oid); + Ensure.ZeroResult(res); + + return new GitObjectMetadata((long)length, objectType); + } + + public static unsafe ICollection git_odb_foreach(ObjectDatabaseHandle 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(OdbStreamHandle stream, byte[] data, int len) + { + int res; + unsafe + { + fixed (byte* p = data) + { + res = NativeMethods.git_odb_stream_write(stream, (IntPtr)p, (UIntPtr)len); + } + } + + Ensure.ZeroResult(res); + } + + public static unsafe ObjectId git_odb_stream_finalize_write(OdbStreamHandle stream) + { + GitOid id; + int res = NativeMethods.git_odb_stream_finalize_write(out id, stream); + Ensure.ZeroResult(res); + + return id; + } + + public static unsafe ObjectId git_odb_write(ObjectDatabaseHandle odb, byte[] data, ObjectType type) + { + 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 unsafe PatchHandle git_patch_from_diff(DiffHandle diff, int idx) + { + 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 unsafe void git_patch_print(PatchHandle patch, NativeMethods.git_diff_line_cb printCallback) + { + int res = NativeMethods.git_patch_print(patch, printCallback, IntPtr.Zero); + Ensure.ZeroResult(res); + } + + public static unsafe Tuple git_patch_line_stats(PatchHandle patch) + { + 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); + } + + #endregion + + #region git_packbuilder_ + + public static unsafe PackBuilderHandle git_packbuilder_new(RepositoryHandle repo) + { + 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_rebase + + public static unsafe RebaseHandle git_rebase_init( + RepositoryHandle repo, + AnnotatedCommitHandle branch, + AnnotatedCommitHandle upstream, + AnnotatedCommitHandle onto, + GitRebaseOptions options) + { + 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 unsafe RebaseHandle git_rebase_open(RepositoryHandle repo, GitRebaseOptions options) + { + git_rebase* rebase = null; + + int result = NativeMethods.git_rebase_open(out rebase, repo, options); + Ensure.ZeroResult(result); + + return new RebaseHandle(rebase, true); + } + + public static unsafe long git_rebase_operation_entrycount(RebaseHandle rebase) + { + return NativeMethods.git_rebase_operation_entrycount(rebase).ConvertToLong(); + } + + public static unsafe long git_rebase_operation_current(RebaseHandle rebase) + { + UIntPtr result = NativeMethods.git_rebase_operation_current(rebase); + + if (result == GIT_REBASE_NO_OPERATION) + { + return RebaseNoOperation; + } + else + { + return result.ConvertToLong(); + } + } + + /// + /// The value from the native layer indicating that no rebase operation is in progress. + /// + private static UIntPtr GIT_REBASE_NO_OPERATION + { + get + { + return UIntPtr.Size == 4 ? new UIntPtr(uint.MaxValue) : new UIntPtr(ulong.MaxValue); + } + } + + 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) + { + git_rebase_operation* ptr; + int result = NativeMethods.git_rebase_next(out ptr, rebase); + if (result == (int)GitErrorCode.IterOver) + { + return null; + } + Ensure.ZeroResult(result); + + return ptr; + } + + public static unsafe GitRebaseCommitResult git_rebase_commit( + RebaseHandle rebase, + Identity author, + Identity committer) + { + Ensure.ArgumentNotNull(rebase, "rebase"); + Ensure.ArgumentNotNull(committer, "committer"); + + using (SignatureHandle committerHandle = committer.BuildNowSignatureHandle()) + using (SignatureHandle authorHandle = author.SafeBuildNowSignatureHandle()) + { + 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; + } + } + + /// + /// 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) + { + Ensure.ArgumentNotNull(rebase, "rebase"); + + int result = NativeMethods.git_rebase_abort(rebase); + Ensure.ZeroResult(result); + } + + public static unsafe void git_rebase_finish( + RebaseHandle rebase, + Identity committer) + { + Ensure.ArgumentNotNull(rebase, "rebase"); + Ensure.ArgumentNotNull(committer, "committer"); + + using (var signatureHandle = committer.BuildNowSignatureHandle()) + { + int result = NativeMethods.git_rebase_finish(rebase, signatureHandle); + Ensure.ZeroResult(result); + } + } + + #endregion + + #region git_reference_ + + public static unsafe ReferenceHandle git_reference_create( + RepositoryHandle repo, + string name, + ObjectId targetId, + bool allowOverwrite, + string logMessage) + { + GitOid oid = targetId.Oid; + git_reference* handle; + + int res = NativeMethods.git_reference_create(out handle, repo, name, ref oid, allowOverwrite, logMessage); + Ensure.ZeroResult(res); + + return new ReferenceHandle(handle, true); + } + + public static unsafe ReferenceHandle git_reference_symbolic_create( + RepositoryHandle repo, + string name, + string target, + bool allowOverwrite, + string logMessage) + { + git_reference* handle; + int res = NativeMethods.git_reference_symbolic_create(out handle, repo, name, target, allowOverwrite, + logMessage); + Ensure.ZeroResult(res); + + return new ReferenceHandle(handle, true); + } + + 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 bool git_reference_is_valid_name(string refname) + { + int res = NativeMethods.git_reference_is_valid_name(refname); + Ensure.BooleanResult(res); + + return (res == 1); + } + + public static unsafe IList git_reference_list(RepositoryHandle repo) + { + var array = new GitStrArrayNative(); + + try + { + int res = NativeMethods.git_reference_list(out array.Array, repo); + Ensure.ZeroResult(res); + + return array.ReadStrings(); + } + finally + { + array.Dispose(); + } + } + + public static unsafe ReferenceHandle git_reference_lookup(RepositoryHandle repo, string name, bool shouldThrowIfNotFound) + { + git_reference* handle; + int res = NativeMethods.git_reference_lookup(out handle, repo, name); + + if (!shouldThrowIfNotFound && res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.ZeroResult(res); + + return new ReferenceHandle(handle, true); + } + + public static unsafe string git_reference_name(git_reference* reference) + { + return NativeMethods.git_reference_name(reference); + } + + public static unsafe void git_reference_remove(RepositoryHandle repo, string name) + { + int res = NativeMethods.git_reference_remove(repo, name); + Ensure.ZeroResult(res); + } + + public static unsafe ObjectId git_reference_target(git_reference* reference) + { + return ObjectId.BuildFromPtr(NativeMethods.git_reference_target(reference)); + } + + public static unsafe ReferenceHandle git_reference_rename( + ReferenceHandle reference, + string newName, + bool allowOverwrite, + string logMessage) + { + git_reference* ref_out; + + int res = NativeMethods.git_reference_rename(out ref_out, reference, newName, allowOverwrite, logMessage); + Ensure.ZeroResult(res); + + return new ReferenceHandle(ref_out, true); + } + + public static unsafe ReferenceHandle git_reference_set_target(ReferenceHandle reference, ObjectId id, string logMessage) + { + GitOid oid = id.Oid; + git_reference* ref_out; + + int res = NativeMethods.git_reference_set_target(out ref_out, reference, ref oid, logMessage); + Ensure.ZeroResult(res); + + return new ReferenceHandle(ref_out, true); + } + + public static unsafe ReferenceHandle git_reference_symbolic_set_target(ReferenceHandle reference, string target, string logMessage) + { + git_reference* ref_out; + + int res = NativeMethods.git_reference_symbolic_set_target(out ref_out, reference, target, logMessage); + Ensure.ZeroResult(res); + + return new ReferenceHandle(ref_out, true); + } + + public static unsafe string git_reference_symbolic_target(git_reference* reference) + { + return NativeMethods.git_reference_symbolic_target(reference); + } + + public static unsafe GitReferenceType git_reference_type(git_reference* reference) + { + return NativeMethods.git_reference_type(reference); + } + + public static unsafe void git_reference_ensure_log(RepositoryHandle repo, string refname) + { + int res = NativeMethods.git_reference_ensure_log(repo, refname); + Ensure.ZeroResult(res); + } + + #endregion + + #region git_reflog_ + + public static unsafe ReflogHandle git_reflog_read(RepositoryHandle repo, string canonicalName) + { + git_reflog* reflog_out; + + int res = NativeMethods.git_reflog_read(out reflog_out, repo, canonicalName); + Ensure.ZeroResult(res); + + return new ReflogHandle(reflog_out, true); + } + + public static unsafe int git_reflog_entrycount(ReflogHandle reflog) + { + return (int)NativeMethods.git_reflog_entrycount(reflog); + } + + 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 unsafe ObjectId git_reflog_entry_id_old(git_reflog_entry* entry) + { + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_old(entry)); + } + + public static unsafe ObjectId git_reflog_entry_id_new(git_reflog_entry* entry) + { + return ObjectId.BuildFromPtr(NativeMethods.git_reflog_entry_id_new(entry)); + } + + public static unsafe Signature git_reflog_entry_committer(git_reflog_entry* entry) + { + return new Signature(NativeMethods.git_reflog_entry_committer(entry)); + } + + public static unsafe string git_reflog_entry_message(git_reflog_entry* entry) + { + return NativeMethods.git_reflog_entry_message(entry); + } + + #endregion + + #region git_refspec + + 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 (var buf = new GitBuf()) + { + int res = NativeMethods.git_refspec_rtransform(buf, refSpecPtr, name); + Ensure.ZeroResult(res); + + return LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; + } + } + + public static unsafe string git_refspec_string(IntPtr refspec) + { + return NativeMethods.git_refspec_string(refspec); + } + + public static unsafe string git_refspec_src(IntPtr refSpec) + { + return NativeMethods.git_refspec_src(refSpec); + } + + public static unsafe string git_refspec_dst(IntPtr refSpec) + { + return NativeMethods.git_refspec_dst(refSpec); + } + + public static unsafe RefSpecDirection git_refspec_direction(IntPtr refSpec) + { + return NativeMethods.git_refspec_direction(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 unsafe TagFetchMode git_remote_autotag(RemoteHandle remote) + { + return (TagFetchMode)NativeMethods.git_remote_autotag(remote); + } + + public static unsafe RemoteHandle git_remote_create(RepositoryHandle repo, string name, string url) + { + git_remote* handle; + int res = NativeMethods.git_remote_create(out handle, repo, name, url); + Ensure.ZeroResult(res); + + return new RemoteHandle(handle, true); + } + + public static unsafe RemoteHandle git_remote_create_with_fetchspec(RepositoryHandle repo, string name, string url, string refspec) + { + git_remote* handle; + int res = NativeMethods.git_remote_create_with_fetchspec(out handle, repo, name, url, refspec); + Ensure.ZeroResult(res); + + return new RemoteHandle(handle, true); + } + + public static unsafe RemoteHandle git_remote_create_anonymous(RepositoryHandle repo, string url) + { + git_remote* handle; + int res = NativeMethods.git_remote_create_anonymous(out handle, repo, url); + Ensure.ZeroResult(res); + + return new RemoteHandle(handle, true); + } + + public static unsafe void git_remote_connect(RemoteHandle remote, GitDirection direction, ref GitRemoteCallbacks remoteCallbacks, ref GitProxyOptions proxyOptions) + { + GitStrArrayManaged customHeaders = new GitStrArrayManaged(); + + try + { + int res = NativeMethods.git_remote_connect(remote, direction, ref remoteCallbacks, ref proxyOptions, ref customHeaders.Array); + Ensure.ZeroResult(res); + } + catch (Exception) + { + customHeaders.Dispose(); + throw; + } + } + + public static unsafe void git_remote_delete(RepositoryHandle repo, string name) + { + int res = NativeMethods.git_remote_delete(repo, name); + + if (res == (int)GitErrorCode.NotFound) + { + return; + } + + Ensure.ZeroResult(res); + } + + public static unsafe git_refspec* git_remote_get_refspec(RemoteHandle remote, int n) + { + return NativeMethods.git_remote_get_refspec(remote, (UIntPtr)n); } - public static void git_ignore_clear_internal_rules(RepositorySafeHandle repo) + public static unsafe int git_remote_refspec_count(RemoteHandle remote) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_ignore_clear_internal_rules(repo); - Ensure.ZeroResult(res); - } + return (int)NativeMethods.git_remote_refspec_count(remote); } - public static bool git_ignore_path_is_ignored(RepositorySafeHandle repo, string path) + public static unsafe IList git_remote_get_fetch_refspecs(RemoteHandle remote) { - using (ThreadAffinity()) + var array = new GitStrArrayNative(); + + try { - int ignored; - int res = NativeMethods.git_ignore_path_is_ignored(out ignored, repo, path); + int res = NativeMethods.git_remote_get_fetch_refspecs(out array.Array, remote); Ensure.ZeroResult(res); - return (ignored != 0); + return array.ReadStrings(); } - } - - #endregion - - #region git_index_ - - public static void git_index_add(IndexSafeHandle index, GitIndexEntry entry) - { - using (ThreadAffinity()) + finally { - int res = NativeMethods.git_index_add(index, entry); - Ensure.ZeroResult(res); + array.Dispose(); } } - public static void git_index_add_bypath(IndexSafeHandle index, FilePath path) + public static unsafe IList git_remote_get_push_refspecs(RemoteHandle remote) { - using (ThreadAffinity()) + var array = new GitStrArrayNative(); + + try { - int res = NativeMethods.git_index_add_bypath(index, path); + int res = NativeMethods.git_remote_get_push_refspecs(out array.Array, remote); Ensure.ZeroResult(res); + + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static Conflict git_index_conflict_get( - IndexSafeHandle index, - Repository repo, - FilePath path) + public static unsafe void git_remote_push(RemoteHandle remote, IEnumerable refSpecs, GitPushOptions opts) { - IndexEntrySafeHandle ancestor, ours, theirs; + var array = new GitStrArrayManaged(); - int res = NativeMethods.git_index_conflict_get( - out ancestor, out ours, out theirs, index, path); + try + { + array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); - if (res == (int)GitErrorCode.NotFound) + int res = NativeMethods.git_remote_push(remote, ref array.Array, opts); + Ensure.ZeroResult(res); + } + finally { - return null; + array.Dispose(); } - - Ensure.ZeroResult(res); - - return new Conflict( - IndexEntry.BuildFromPtr(repo, ancestor), - IndexEntry.BuildFromPtr(repo, ours), - IndexEntry.BuildFromPtr(repo, theirs)); } - public static int git_index_entrycount(IndexSafeHandle index) + public static unsafe void git_remote_set_url(RepositoryHandle repo, string remote, string url) { - UIntPtr count = NativeMethods.git_index_entrycount(index); - if ((long)count > int.MaxValue) - { - throw new LibGit2SharpException("Index entry count exceeds size of int"); - } - return (int)count; + int res = NativeMethods.git_remote_set_url(repo, remote, url); + Ensure.ZeroResult(res); } - public static StageLevel git_index_entry_stage(IndexEntrySafeHandle index) + public static unsafe void git_remote_add_fetch(RepositoryHandle repo, string remote, string url) { - return (StageLevel)NativeMethods.git_index_entry_stage(index); + int res = NativeMethods.git_remote_add_fetch(repo, remote, url); + Ensure.ZeroResult(res); } - public static void git_index_free(IntPtr index) + public static unsafe void git_remote_set_pushurl(RepositoryHandle repo, string remote, string url) { - NativeMethods.git_index_free(index); + int res = NativeMethods.git_remote_set_pushurl(repo, remote, url); + Ensure.ZeroResult(res); } - public static IndexEntrySafeHandle git_index_get_byindex(IndexSafeHandle index, UIntPtr n) + public static unsafe void git_remote_add_push(RepositoryHandle repo, string remote, string url) { - return NativeMethods.git_index_get_byindex(index, n); + int res = NativeMethods.git_remote_add_push(repo, remote, url); + Ensure.ZeroResult(res); } - public static IndexEntrySafeHandle git_index_get_bypath(IndexSafeHandle index, FilePath path, int stage) + public static unsafe void git_remote_fetch( + RemoteHandle remote, IEnumerable refSpecs, + GitFetchOptions fetchOptions, string logMessage) { - IndexEntrySafeHandle handle = NativeMethods.git_index_get_bypath(index, path, stage); + var array = new GitStrArrayManaged(); + + try + { + array = GitStrArrayManaged.BuildFrom(refSpecs.ToArray()); - return handle.IsZero ? null : handle; + int res = NativeMethods.git_remote_fetch(remote, ref array.Array, fetchOptions, logMessage); + Ensure.ZeroResult(res); + } + finally + { + array.Dispose(); + } } - public static bool git_index_has_conflicts(IndexSafeHandle index) + public static bool git_remote_is_valid_name(string refname) { - int res = NativeMethods.git_index_has_conflicts(index); + int res = NativeMethods.git_remote_is_valid_name(refname); Ensure.BooleanResult(res); - return res != 0; + return (res == 1); } - public static IndexSafeHandle git_index_open(FilePath indexpath) + public static unsafe IList git_remote_list(RepositoryHandle repo) { - using (ThreadAffinity()) + var array = new GitStrArrayNative(); + + try { - IndexSafeHandle handle; - int res = NativeMethods.git_index_open(out handle, indexpath); + int res = NativeMethods.git_remote_list(out array.Array, repo); Ensure.ZeroResult(res); - return handle; + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static void git_index_remove(IndexSafeHandle index, FilePath path, int stage) + public static unsafe IEnumerable git_remote_ls(Repository repository, RemoteHandle remote) { - using (ThreadAffinity()) + git_remote_head** heads; + UIntPtr count; + + int res = NativeMethods.git_remote_ls(out heads, out count, remote); + Ensure.ZeroResult(res); + + var intCount = checked(count.ToUInt32()); + var directRefs = new Dictionary(); + var symRefs = new Dictionary(); + + for (int i = 0; i < intCount; i++) { - int res = NativeMethods.git_index_remove(index, path, stage); - Ensure.ZeroResult(res); + 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 (string.IsNullOrEmpty(name)) + { + throw new InvalidOperationException("Not expecting null value for reference name."); + } + + if (!string.IsNullOrEmpty(symRefTarget)) + { + symRefs.Add(name, symRefTarget); + } + else + { + directRefs.Add(name, new DirectReference(name, repository, new ObjectId(currentHead->Oid.Id))); + } } - } - public static void git_index_write(IndexSafeHandle index) - { - using (ThreadAffinity()) + for (int i = 0; i < symRefs.Count; i++) { - int res = NativeMethods.git_index_write(index); - Ensure.ZeroResult(res); + var symRef = symRefs.ElementAt(i); + + if (!directRefs.ContainsKey(symRef.Value)) + { + throw new InvalidOperationException("Symbolic reference target not found in direct reference results."); + } + + 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 ObjectId git_tree_create_fromindex(Index index) + public static unsafe RemoteHandle git_remote_lookup(RepositoryHandle repo, string name, bool throwsIfNotFound) { - using (ThreadAffinity()) - { - GitOid treeOid; - int res = NativeMethods.git_index_write_tree(out treeOid, index.Handle); - Ensure.ZeroResult(res); + git_remote* handle; + int res = NativeMethods.git_remote_lookup(out handle, repo, name); - return treeOid; + if (res == (int)GitErrorCode.NotFound && !throwsIfNotFound) + { + return null; } - } - #endregion + Ensure.ZeroResult(res); + return new RemoteHandle(handle, true); + } - #region git_merge_ + public static unsafe string git_remote_name(RemoteHandle remote) + { + return NativeMethods.git_remote_name(remote); + } - public static ObjectId git_merge_base(RepositorySafeHandle repo, Commit first, Commit second) + public static unsafe void git_remote_rename(RepositoryHandle repo, string name, string new_name, RemoteRenameFailureHandler callback) { - using (ThreadAffinity()) - using (var osw1 = new ObjectSafeWrapper(first.Id, repo)) - using (var osw2 = new ObjectSafeWrapper(second.Id, repo)) + if (callback == null) + { + callback = problem => { }; + } + + var array = new GitStrArrayNative(); + + try { - GitOid ret; - int res = NativeMethods.git_merge_base(out ret, repo, osw1.ObjectPtr, osw2.ObjectPtr); + int res = NativeMethods.git_remote_rename(ref array.Array, + repo, + name, + new_name); if (res == (int)GitErrorCode.NotFound) { - return null; + throw new NotFoundException("Remote '{0}' does not exist and cannot be renamed.", name); } Ensure.ZeroResult(res); - return ret; + foreach (var item in array.ReadStrings()) + { + callback(item); + } + } + finally + { + array.Dispose(); } } - #endregion + public static unsafe void git_remote_set_autotag(RepositoryHandle repo, string remote, TagFetchMode value) + { + NativeMethods.git_remote_set_autotag(repo, remote, value); + } - #region git_message_ + public static unsafe string git_remote_url(RemoteHandle remote) + { + return NativeMethods.git_remote_url(remote); + } - public static string git_message_prettify(string message) + public static unsafe string git_remote_pushurl(RemoteHandle remote) { - using (ThreadAffinity()) - { - int bufSize = NativeMethods.git_message_prettify(null, UIntPtr.Zero, message, false); - Ensure.Int32Result(bufSize); + return NativeMethods.git_remote_pushurl(remote); + } - var buffer = new byte[bufSize]; + #endregion - int res = NativeMethods.git_message_prettify(buffer, (UIntPtr)buffer.Length, message, false); - Ensure.Int32Result(res); + #region git_repository_ - return Utf8Marshaler.Utf8FromBuffer(buffer) ?? string.Empty; - } + public static FilePath git_repository_discover(FilePath start_path) + { + return ConvertPath(buf => NativeMethods.git_repository_discover(buf, start_path, false, null)); } - #endregion + public static unsafe bool git_repository_head_detached(RepositoryHandle repo) + { + return RepositoryStateChecker(repo, NativeMethods.git_repository_head_detached); + } - #region git_note_ + 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 ObjectId git_note_create( - RepositorySafeHandle repo, - Signature author, - Signature committer, - string notes_ref, - ObjectId targetId, - string note, - bool force) + public static bool git_repository_head_unborn(RepositoryHandle repo) { - using (ThreadAffinity()) - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) - { - GitOid noteOid; - GitOid oid = targetId.Oid; + return RepositoryStateChecker(repo, NativeMethods.git_repository_head_unborn); + } - int res = NativeMethods.git_note_create(out noteOid, repo, authorHandle, committerHandle, notes_ref, ref oid, note, force ? 1 : 0); - Ensure.ZeroResult(res); + public static unsafe IndexHandle git_repository_index(RepositoryHandle repo) + { + git_index* handle; + int res = NativeMethods.git_repository_index(out handle, repo); + Ensure.ZeroResult(res); - return noteOid; - } + return new IndexHandle(handle, true); } - public static string git_note_default_ref(RepositorySafeHandle repo) + public static unsafe RepositoryHandle git_repository_init_ext( + FilePath workdirPath, + FilePath gitdirPath, + bool isBare) { - using (ThreadAffinity()) + using (var opts = GitRepositoryInitOptions.BuildFrom(workdirPath, isBare)) { - string notes_ref; - int res = NativeMethods.git_note_default_ref(out notes_ref, repo); + git_repository* repo; + int res = NativeMethods.git_repository_init_ext(out repo, gitdirPath, opts); Ensure.ZeroResult(res); - return notes_ref; + return new RepositoryHandle(repo, true); } } - public static ICollection git_note_foreach(RepositorySafeHandle repo, string notes_ref, Func resultSelector) + public static unsafe bool git_repository_is_bare(RepositoryHandle repo) { - 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 RepositoryStateChecker(repo, NativeMethods.git_repository_is_bare); } - public static void git_note_free(IntPtr note) + public static unsafe bool git_repository_is_shallow(RepositoryHandle repo) { - NativeMethods.git_note_free(note); + return RepositoryStateChecker(repo, NativeMethods.git_repository_is_shallow); } - public static string git_note_message(NoteSafeHandle note) + public static unsafe void git_repository_state_cleanup(RepositoryHandle repo) { - return NativeMethods.git_note_message(note); + int res = NativeMethods.git_repository_state_cleanup(repo); + Ensure.ZeroResult(res); } - public static ObjectId git_note_oid(NoteSafeHandle note) + public static unsafe ICollection git_repository_mergehead_foreach( + RepositoryHandle repo, + Func resultSelector) { - return NativeMethods.git_note_oid(note).MarshalAsObjectId(); + 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 NoteSafeHandle git_note_read(RepositorySafeHandle repo, string notes_ref, ObjectId id) + public static unsafe string git_repository_message(RepositoryHandle repo) { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - GitOid oid = id.Oid; - NoteSafeHandle note; - - int res = NativeMethods.git_note_read(out note, repo, notes_ref, ref oid); - + int res = NativeMethods.git_repository_message(buf, repo); if (res == (int)GitErrorCode.NotFound) { return null; } - Ensure.ZeroResult(res); - return note; + return LaxUtf8Marshaler.FromNative(buf.ptr); } } - public static void git_note_remove(RepositorySafeHandle repo, string notes_ref, Signature author, Signature committer, ObjectId targetId) + public static unsafe ObjectDatabaseHandle git_repository_odb(RepositoryHandle repo) { - using (ThreadAffinity()) - using (SignatureSafeHandle authorHandle = author.BuildHandle()) - using (SignatureSafeHandle committerHandle = committer.BuildHandle()) - { - GitOid oid = targetId.Oid; - - int res = NativeMethods.git_note_remove(repo, notes_ref, authorHandle, committerHandle, ref oid); - - if (res == (int)GitErrorCode.NotFound) - { - return; - } + git_odb* handle; + int res = NativeMethods.git_repository_odb(out handle, repo); + Ensure.ZeroResult(res); - Ensure.ZeroResult(res); - } + return new ObjectDatabaseHandle(handle, true); } - #endregion + public static unsafe RepositoryHandle git_repository_open(string path) + { + git_repository* repo; + int res = NativeMethods.git_repository_open(out repo, path); - #region git_object_ + if (res == (int)GitErrorCode.NotFound) + { + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); + } - public static ObjectId git_object_id(GitObjectSafeHandle obj) - { - return NativeMethods.git_object_id(obj).MarshalAsObjectId(); - } + Ensure.ZeroResult(res); - public static void git_object_free(IntPtr obj) - { - NativeMethods.git_object_free(obj); + return new RepositoryHandle(repo, true); } - public static GitObjectSafeHandle git_object_lookup(RepositorySafeHandle repo, ObjectId id, GitObjectType type) + public static unsafe RepositoryHandle git_repository_new() { - using (ThreadAffinity()) - { - 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; + git_repository* repo; + int res = NativeMethods.git_repository_new(out repo); - default: - Ensure.ZeroResult(res); - break; - } + Ensure.ZeroResult(res); - return handle; - } + return new RepositoryHandle(repo, true); } - public static GitObjectSafeHandle git_object_peel(RepositorySafeHandle repo, ObjectId id, GitObjectType type, bool throwsIfCanNotPeel) + public static unsafe void git_repository_open_ext(string path, RepositoryOpenFlags flags, string ceilingDirs) { - using (ThreadAffinity()) - { - GitObjectSafeHandle peeled; - int res; - - using (var obj = new ObjectSafeWrapper(id, repo)) - { - res = NativeMethods.git_object_peel(out peeled, obj.ObjectPtr, type); - } + int res; + git_repository* repo; - if (!throwsIfCanNotPeel && - (res == (int)GitErrorCode.NotFound || res == (int)GitErrorCode.Ambiguous)) - { - return null; - } + res = NativeMethods.git_repository_open_ext(out repo, path, flags, ceilingDirs); + NativeMethods.git_repository_free(repo); - Ensure.ZeroResult(res); - return peeled; + if (res == (int)GitErrorCode.NotFound) + { + throw new RepositoryNotFoundException("Path '{0}' doesn't point at a valid Git repository or workdir.", + path); } - } - public static GitObjectType git_object_type(GitObjectSafeHandle obj) - { - return NativeMethods.git_object_type(obj); + Ensure.ZeroResult(res); } - #endregion - - #region git_odb_ - - public static void git_odb_add_backend(ObjectDatabaseSafeHandle odb, IntPtr backend, int priority) + public static unsafe FilePath git_repository_path(RepositoryHandle repo) { - Ensure.ZeroResult(NativeMethods.git_odb_add_backend(odb, backend, priority)); + return NativeMethods.git_repository_path(repo); } - public static IntPtr git_odb_backend_malloc(IntPtr backend, UIntPtr len) + public static unsafe int git_repository_set_config(RepositoryHandle repo, ConfigurationHandle config) { - IntPtr toReturn = NativeMethods.git_odb_backend_malloc(backend, 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); - } - - return toReturn; + return NativeMethods.git_repository_set_config(repo, config); } - public static bool git_odb_exists(ObjectDatabaseSafeHandle odb, ObjectId id) + public static unsafe void git_repository_set_ident(RepositoryHandle repo, string name, string email) { - GitOid oid = id.Oid; - - int res = NativeMethods.git_odb_exists(odb, ref oid); - Ensure.BooleanResult(res); - - return (res == 1); + int res = NativeMethods.git_repository_set_ident(repo, name, email); + Ensure.ZeroResult(res); } - public static void git_odb_free(IntPtr odb) + public static unsafe int git_repository_set_index(RepositoryHandle repo, IndexHandle index) { - NativeMethods.git_odb_free(odb); + return NativeMethods.git_repository_set_index(repo, index); } - #endregion - - #region git_push_ - - public static void git_push_add_refspec(PushSafeHandle push, string pushRefSpec) + public static unsafe void git_repository_set_workdir(RepositoryHandle repo, FilePath workdir) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_add_refspec(push, pushRefSpec); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_repository_set_workdir(repo, workdir, false); + Ensure.ZeroResult(res); } - public static void git_push_finish(PushSafeHandle push) + public static unsafe CurrentOperation git_repository_state(RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_finish(push); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_repository_state(repo); + Ensure.Int32Result(res); + return (CurrentOperation)res; } - public static void git_push_free(IntPtr push) + public static unsafe FilePath git_repository_workdir(RepositoryHandle repo) { - NativeMethods.git_push_free(push); + return NativeMethods.git_repository_workdir(repo); } - public static PushSafeHandle git_push_new(RemoteSafeHandle remote) + public static FilePath git_repository_workdir(IntPtr repo) { - using (ThreadAffinity()) - { - PushSafeHandle handle; - int res = NativeMethods.git_push_new(out handle, remote); - Ensure.ZeroResult(res); - return handle; - } + return NativeMethods.git_repository_workdir(repo); } - public static void git_push_status_foreach(PushSafeHandle push, NativeMethods.push_status_foreach_cb status_cb) + public static unsafe void git_repository_set_head_detached(RepositoryHandle repo, ObjectId commitish) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_status_foreach(push, status_cb, IntPtr.Zero); - Ensure.ZeroResult(res); - } + GitOid oid = commitish.Oid; + int res = NativeMethods.git_repository_set_head_detached(repo, ref oid); + Ensure.ZeroResult(res); } - public static bool git_push_unpack_ok(PushSafeHandle push) + public static unsafe void git_repository_set_head_detached_from_annotated(RepositoryHandle repo, AnnotatedCommitHandle commit) { - int res = NativeMethods.git_push_unpack_ok(push); - return res == 1; + int res = NativeMethods.git_repository_set_head_detached_from_annotated(repo, commit); + Ensure.ZeroResult(res); } - public static void git_push_update_tips(PushSafeHandle push) + public static unsafe void git_repository_set_head(RepositoryHandle repo, string refname) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_push_update_tips(push); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_repository_set_head(repo, refname); + Ensure.ZeroResult(res); } #endregion - #region git_reference_ + #region git_reset_ - public static ReferenceSafeHandle git_reference_create_oid(RepositorySafeHandle repo, string name, ObjectId targetId, bool allowOverwrite) + public static unsafe void git_reset( + RepositoryHandle repo, + ObjectId committishId, + ResetMode resetKind, + ref GitCheckoutOpts checkoutOptions) { - using (ThreadAffinity()) + using (var osw = new ObjectSafeWrapper(committishId, repo)) { - GitOid oid = targetId.Oid; - ReferenceSafeHandle handle; - - int res = NativeMethods.git_reference_create(out handle, repo, name, ref oid, allowOverwrite); + int res = NativeMethods.git_reset(repo, osw.ObjectPtr, resetKind, ref checkoutOptions); Ensure.ZeroResult(res); - - return handle; } } - public static ReferenceSafeHandle git_reference_create_symbolic(RepositorySafeHandle repo, string name, string target, bool allowOverwrite) - { - using (ThreadAffinity()) - { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_symbolic_create(out handle, repo, name, target, allowOverwrite); - Ensure.ZeroResult(res); + #endregion - return handle; - } - } + #region git_revert_ - public static void git_reference_delete(ReferenceSafeHandle reference) + 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_reference_delete(reference); - reference.SetHandleAsInvalid(); - + int res = NativeMethods.git_revert(repo, nativeCommit, opts); Ensure.ZeroResult(res); } } - public static ICollection git_reference_foreach_glob( - RepositorySafeHandle repo, - string glob, - GitReferenceType flags, - Func resultSelector) - { - return git_foreach(resultSelector, c => NativeMethods.git_reference_foreach_glob(repo, glob, flags, (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); - Ensure.BooleanResult(res); - - return (res == 1); - } - - public static IList git_reference_list(RepositorySafeHandle repo, GitReferenceType flags) + internal static unsafe IndexHandle git_revert_commit(RepositoryHandle repo, ObjectHandle revertCommit, ObjectHandle ourCommit, uint mainline, GitMergeOpts opts, out bool earlyStop) { - using (ThreadAffinity()) + git_index* index; + int res = NativeMethods.git_revert_commit(out index, repo, revertCommit, ourCommit, mainline, ref opts); + if (res == (int)GitErrorCode.MergeConflict) { - UnSafeNativeMethods.git_strarray arr; - int res = UnSafeNativeMethods.git_reference_list(out arr, repo, flags); + earlyStop = true; + } + else + { + earlyStop = false; Ensure.ZeroResult(res); - - return Libgit2UnsafeHelper.BuildListOf(arr); } + return new IndexHandle(index, true); } + #endregion + + #region git_revparse_ - public static ReferenceSafeHandle git_reference_lookup(RepositorySafeHandle repo, string name, bool shouldThrowIfNotFound) + public static unsafe Tuple git_revparse_ext(RepositoryHandle repo, string objectish) { - using (ThreadAffinity()) - { - ReferenceSafeHandle handle; - int res = NativeMethods.git_reference_lookup(out handle, repo, name); + git_object* obj; + git_reference* reference; + int res = NativeMethods.git_revparse_ext(out obj, out reference, repo, objectish); - if (!shouldThrowIfNotFound && res == (int)GitErrorCode.NotFound) - { + switch (res) + { + case (int)GitErrorCode.NotFound: return null; - } - Ensure.ZeroResult(res); + case (int)GitErrorCode.Ambiguous: + throw new AmbiguousSpecificationException("Provided abbreviated ObjectId '{0}' is too short.", + objectish); - return handle; + default: + Ensure.ZeroResult(res); + break; } + + return new Tuple(new ObjectHandle(obj, true), new ReferenceHandle(reference, true)); } - public static string git_reference_name(ReferenceSafeHandle reference) + public static ObjectHandle git_revparse_single(RepositoryHandle repo, string objectish) { - return NativeMethods.git_reference_name(reference); + var handles = git_revparse_ext(repo, objectish); + + if (handles == null) + { + return null; + } + + handles.Item2.Dispose(); + + return handles.Item1; } - public static ObjectId git_reference_oid(ReferenceSafeHandle reference) + #endregion + + #region git_revwalk_ + + public static unsafe void git_revwalk_hide(RevWalkerHandle walker, ObjectId commit_id) { - return NativeMethods.git_reference_target(reference).MarshalAsObjectId(); + GitOid oid = commit_id.Oid; + int res = NativeMethods.git_revwalk_hide(walker, ref oid); + Ensure.ZeroResult(res); } - public static void git_reference_rename(ReferenceSafeHandle reference, string newName, bool allowOverwrite) + public static unsafe RevWalkerHandle git_revwalk_new(RepositoryHandle repo) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_reference_rename(reference, newName, allowOverwrite); - Ensure.ZeroResult(res); - } + git_revwalk* handle; + int res = NativeMethods.git_revwalk_new(out handle, repo); + Ensure.ZeroResult(res); + + return new RevWalkerHandle(handle, true); } - public static ReferenceSafeHandle git_reference_resolve(ReferenceSafeHandle reference) + public static unsafe ObjectId git_revwalk_next(RevWalkerHandle walker) { - using (ThreadAffinity()) - { - ReferenceSafeHandle resolvedHandle; - int res = NativeMethods.git_reference_resolve(out resolvedHandle, reference); + GitOid ret; + int res = NativeMethods.git_revwalk_next(out ret, walker); - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + if (res == (int)GitErrorCode.IterOver) + { + return null; + } - Ensure.ZeroResult(res); + Ensure.ZeroResult(res); - return resolvedHandle; - } + return ret; } - public static void git_reference_set_oid(ReferenceSafeHandle reference, ObjectId id) + public static unsafe void git_revwalk_push(RevWalkerHandle walker, ObjectId id) { - using (ThreadAffinity()) - { - GitOid oid = id.Oid; - int res = NativeMethods.git_reference_set_target(reference, 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_reference_set_target(ReferenceSafeHandle reference, string target) + public static unsafe void git_revwalk_reset(RevWalkerHandle walker) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_reference_symbolic_set_target(reference, target); - Ensure.ZeroResult(res); - } + NativeMethods.git_revwalk_reset(walker); } - public static string git_reference_target(ReferenceSafeHandle reference) + public static unsafe int git_revwalk_sorting(RevWalkerHandle walker, CommitSortStrategies options) { - return NativeMethods.git_reference_symbolic_target(reference); + return NativeMethods.git_revwalk_sorting(walker, options); } - public static GitReferenceType git_reference_type(ReferenceSafeHandle reference) + public static unsafe int git_revwalk_simplify_first_parent(RevWalkerHandle walker) { - return NativeMethods.git_reference_type(reference); + return NativeMethods.git_revwalk_simplify_first_parent(walker); } #endregion - #region git_refspec + #region git_signature_ - public static string git_fetchspec_rtransform(GitFetchSpecHandle refSpecPtr, string name) + public static unsafe SignatureHandle git_signature_new(string name, string email, DateTimeOffset when) { - using (ThreadAffinity()) - { - // libgit2 API does not support querying for required buffer size. - // Use a sufficiently large buffer until it does. - var buffer = new byte[1024]; + git_signature* ptr; - // TODO: Use code pattern similar to Proxy.git_message_prettify() when available - int res = NativeMethods.git_refspec_rtransform(buffer, (UIntPtr)buffer.Length, refSpecPtr, name); - Ensure.ZeroResult(res); + int res = NativeMethods.git_signature_new(out ptr, name, email, when.ToUnixTimeSeconds(), + (int)when.Offset.TotalMinutes); + Ensure.ZeroResult(res); - return Utf8Marshaler.Utf8FromBuffer(buffer) ?? string.Empty; - } + return new SignatureHandle(ptr, true); } - #endregion - - #region git_remote_ - - public static RemoteSafeHandle git_remote_create(RepositorySafeHandle repo, string name, string url) + public static unsafe SignatureHandle git_signature_now(string name, string email) { - using (ThreadAffinity()) - { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_create(out handle, repo, name, url); - 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 void git_remote_connect(RemoteSafeHandle remote, GitDirection direction) + public static unsafe git_signature* git_signature_dup(git_signature* sig) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_connect(remote, direction); - Ensure.ZeroResult(res); - } + git_signature* handle; + int res = NativeMethods.git_signature_dup(out handle, sig); + Ensure.ZeroResult(res); + return handle; } - public static void git_remote_disconnect(RemoteSafeHandle remote) - { - using (ThreadAffinity()) - { - NativeMethods.git_remote_disconnect(remote); - } - } + #endregion - public static GitFetchSpecHandle git_remote_fetchspec(RemoteSafeHandle remote) - { - return NativeMethods.git_remote_fetchspec(remote); - } + #region git_stash_ - public static void git_remote_download(RemoteSafeHandle remote, TransferProgressHandler onTransferProgress) + public static unsafe ObjectId git_stash_save( + RepositoryHandle repo, + Signature stasher, + string prettifiedMessage, + StashModifiers options) { - using (ThreadAffinity()) + using (SignatureHandle sigHandle = stasher.BuildHandle()) { - NativeMethods.git_transfer_progress_callback cb = TransferCallbacks.GenerateCallback(onTransferProgress); + GitOid stashOid; - int res = NativeMethods.git_remote_download(remote, cb, IntPtr.Zero); - Ensure.ZeroResult(res); + int res = NativeMethods.git_stash_save(out stashOid, repo, sigHandle, prettifiedMessage, options); + + if (res == (int)GitErrorCode.NotFound) + { + return null; + } + + Ensure.Int32Result(res); + + return new ObjectId(stashOid); } } - public static void git_remote_free(IntPtr remote) + public static unsafe ICollection git_stash_foreach( + RepositoryHandle repo, + Func resultSelector) { - NativeMethods.git_remote_free(remote); + 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 bool git_remote_is_valid_name(string refname) + public static unsafe void git_stash_drop(RepositoryHandle repo, int index) { - int res = NativeMethods.git_remote_is_valid_name(refname); + int res = NativeMethods.git_stash_drop(repo, (UIntPtr)index); Ensure.BooleanResult(res); - - return (res == 1); } - public static IList git_remote_list(RepositorySafeHandle repo) + private static StashApplyStatus get_stash_status(int res) { - using (ThreadAffinity()) + if (res == (int)GitErrorCode.Conflict) { - UnSafeNativeMethods.git_strarray arr; - int res = UnSafeNativeMethods.git_remote_list(out arr, repo); - Ensure.ZeroResult(res); + return StashApplyStatus.Conflicts; + } - return Libgit2UnsafeHelper.BuildListOf(arr); + if (res == (int)GitErrorCode.Uncommitted) + { + return StashApplyStatus.UncommittedChanges; } - } - public static void git_remote_ls(RemoteSafeHandle remote, NativeMethods.git_headlist_cb headlist_cb) - { - using (ThreadAffinity()) + if (res == (int)GitErrorCode.NotFound) { - int res = NativeMethods.git_remote_ls(remote, headlist_cb, IntPtr.Zero); - Ensure.ZeroResult(res); + return StashApplyStatus.NotFound; } + + Ensure.ZeroResult(res); + return StashApplyStatus.Applied; + } + + 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 RemoteSafeHandle git_remote_load(RepositorySafeHandle repo, string name, bool throwsIfNotFound) + public static unsafe StashApplyStatus git_stash_pop( + RepositoryHandle repo, + int index, + GitStashApplyOpts opts) + { + return get_stash_status(NativeMethods.git_stash_pop(repo, (UIntPtr)index, opts)); + } + + #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) { - RemoteSafeHandle handle; - int res = NativeMethods.git_remote_load(out handle, repo, name); + case (int)GitErrorCode.NotFound: + return FileStatus.Nonexistent; - if (res == (int)GitErrorCode.NotFound && !throwsIfNotFound) - { - return null; - } + 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); - Ensure.ZeroResult(res); - return handle; + default: + Ensure.ZeroResult(res); + break; } + + return status; } - public static string git_remote_name(RemoteSafeHandle remote) + public static unsafe StatusListHandle git_status_list_new(RepositoryHandle repo, GitStatusOptions options) { - return NativeMethods.git_remote_name(remote); + 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 void git_remote_save(RemoteSafeHandle remote) + public static unsafe int git_status_list_entrycount(StatusListHandle list) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_save(remote); - Ensure.ZeroResult(res); - } + int res = NativeMethods.git_status_list_entrycount(list); + Ensure.Int32Result(res); + return res; } - public static void git_remote_set_autotag(RemoteSafeHandle remote, TagFetchMode value) + public static unsafe git_status_entry* git_status_byindex(StatusListHandle list, long idx) { - NativeMethods.git_remote_set_autotag(remote, value); + return NativeMethods.git_status_byindex(list, (UIntPtr)idx); } - public static void git_remote_set_fetchspec(RemoteSafeHandle remote, string fetchspec) + #endregion + + #region git_submodule_ + + /// + /// Returns a handle to the corresponding submodule, + /// or an invalid handle if a submodule is not found. + /// + public static unsafe SubmoduleHandle git_submodule_lookup(RepositoryHandle repo, string name) { - using (ThreadAffinity()) + git_submodule* submodule; + var res = NativeMethods.git_submodule_lookup(out submodule, repo, name); + + switch (res) { - int res = NativeMethods.git_remote_set_fetchspec(remote, fetchspec); - Ensure.ZeroResult(res); + case (int)GitErrorCode.NotFound: + case (int)GitErrorCode.Exists: + case (int)GitErrorCode.OrphanedHead: + return null; + + default: + Ensure.ZeroResult(res); + return new SubmoduleHandle(submodule, true); } } - public static void git_remote_set_callbacks(RemoteSafeHandle remote, ref GitRemoteCallbacks callbacks) + public static unsafe string git_submodule_resolve_url(RepositoryHandle repo, string url) { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - int res = NativeMethods.git_remote_set_callbacks(remote, ref callbacks); + int res = NativeMethods.git_submodule_resolve_url(buf, repo, url); + Ensure.ZeroResult(res); + return LaxUtf8Marshaler.FromNative(buf.ptr); } } - public static void git_remote_set_cred_acquire_cb(RemoteSafeHandle remote, NativeMethods.git_cred_acquire_cb cred_acquire_cb, IntPtr payload) + public static unsafe ICollection git_submodule_foreach(RepositoryHandle repo, Func resultSelector) { - NativeMethods.git_remote_set_cred_acquire_cb(remote, cred_acquire_cb, payload); + return git_foreach(resultSelector, c => NativeMethods.git_submodule_foreach(repo, (x, y, p) => c(x, y, p), IntPtr.Zero)); } - public static void git_remote_update_tips(RemoteSafeHandle remote) + public static unsafe void git_submodule_add_to_index(SubmoduleHandle submodule, bool write_index) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_remote_update_tips(remote); - Ensure.ZeroResult(res); - } + var res = NativeMethods.git_submodule_add_to_index(submodule, write_index); + Ensure.ZeroResult(res); } - public static string git_remote_url(RemoteSafeHandle remote) + public static unsafe void git_submodule_update(SubmoduleHandle submodule, bool init, ref GitSubmoduleUpdateOptions options) { - return NativeMethods.git_remote_url(remote); + var res = NativeMethods.git_submodule_update(submodule, init, ref options); + Ensure.ZeroResult(res); } - #endregion - - #region git_repository_ - - public static FilePath git_repository_discover(FilePath start_path) + public static unsafe string git_submodule_path(SubmoduleHandle submodule) { - return ConvertPath((buffer, bufSize) => NativeMethods.git_repository_discover(buffer, bufSize, start_path, false, null)); + return NativeMethods.git_submodule_path(submodule); } - public static bool git_repository_head_detached(RepositorySafeHandle repo) + public static unsafe string git_submodule_url(SubmoduleHandle submodule) { - return RepositoryStateChecker(repo, NativeMethods.git_repository_head_detached); + return NativeMethods.git_submodule_url(submodule); } - public static ICollection git_repository_fetchhead_foreach( - RepositorySafeHandle repo, - Func resultSelector) + public static unsafe ObjectId git_submodule_index_id(SubmoduleHandle submodule) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_fetchhead_foreach( - repo, - (IntPtr w, IntPtr x, ref GitOid y, bool z, IntPtr p) - => c(Utf8Marshaler.FromNative(w), Utf8Marshaler.FromNative(x), y, z, p), IntPtr.Zero), - GitErrorCode.NotFound); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_index_id(submodule)); } - public static void git_repository_free(IntPtr repo) + public static unsafe ObjectId git_submodule_head_id(SubmoduleHandle submodule) { - NativeMethods.git_repository_free(repo); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_head_id(submodule)); } - public static bool git_repository_head_orphan(RepositorySafeHandle repo) + public static unsafe ObjectId git_submodule_wd_id(SubmoduleHandle submodule) { - return RepositoryStateChecker(repo, NativeMethods.git_repository_head_orphan); + return ObjectId.BuildFromPtr(NativeMethods.git_submodule_wd_id(submodule)); } - public static IndexSafeHandle git_repository_index(RepositorySafeHandle repo) + public static unsafe SubmoduleIgnore git_submodule_ignore(SubmoduleHandle submodule) { - using (ThreadAffinity()) - { - IndexSafeHandle handle; - int res = NativeMethods.git_repository_index(out handle, repo); - Ensure.ZeroResult(res); - - return handle; - } + return NativeMethods.git_submodule_ignore(submodule); } - public static RepositorySafeHandle git_repository_init(FilePath path, bool isBare) + public static unsafe SubmoduleUpdate git_submodule_update_strategy(SubmoduleHandle submodule) { - using (ThreadAffinity()) - { - RepositorySafeHandle repo; - int res = NativeMethods.git_repository_init(out repo, path, isBare); - Ensure.ZeroResult(res); - - return repo; - } + return NativeMethods.git_submodule_update_strategy(submodule); } - public static bool git_repository_is_bare(RepositorySafeHandle repo) + public static unsafe SubmoduleRecurse git_submodule_fetch_recurse_submodules(SubmoduleHandle submodule) { - return RepositoryStateChecker(repo, NativeMethods.git_repository_is_bare); + return NativeMethods.git_submodule_fetch_recurse_submodules(submodule); } - public static bool git_repository_is_empty(RepositorySafeHandle repo) + public static unsafe void git_submodule_reload(SubmoduleHandle submodule) { - return RepositoryStateChecker(repo, NativeMethods.git_repository_is_empty); + var res = NativeMethods.git_submodule_reload(submodule, false); + Ensure.ZeroResult(res); } - public static void git_repository_merge_cleanup(RepositorySafeHandle repo) + public static unsafe SubmoduleStatus git_submodule_status(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_repository_merge_cleanup(repo); - Ensure.ZeroResult(res); - } + SubmoduleStatus status; + var res = NativeMethods.git_submodule_status(out status, repo, name, GitSubmoduleIgnore.Unspecified); + Ensure.ZeroResult(res); + return status; } - public static ICollection git_repository_mergehead_foreach( - RepositorySafeHandle repo, - Func resultSelector) + public static unsafe void git_submodule_init(SubmoduleHandle submodule, bool overwrite) { - return git_foreach( - resultSelector, - c => NativeMethods.git_repository_mergehead_foreach( - repo, (ref GitOid x, IntPtr p) => c(x, p), IntPtr.Zero), - GitErrorCode.NotFound); + var res = NativeMethods.git_submodule_init(submodule, overwrite); + Ensure.ZeroResult(res); } - public static string git_repository_message(RepositorySafeHandle repo) - { - using (ThreadAffinity()) - { - int bufSize = NativeMethods.git_repository_message(null, (UIntPtr)0, repo); + #endregion - if (bufSize == (int)GitErrorCode.NotFound) - { - return null; - } + #region git_tag_ - Ensure.Int32Result(bufSize); + public static unsafe ObjectId git_tag_annotation_create( + RepositoryHandle repo, + string name, + GitObject target, + Signature tagger, + string message) + { + using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) + using (SignatureHandle sigHandle = tagger.BuildHandle()) + { + GitOid oid; + int res = NativeMethods.git_tag_annotation_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message); + Ensure.ZeroResult(res); - byte[] buf = new byte[bufSize]; - int len = NativeMethods.git_repository_message(buf, (UIntPtr)bufSize, repo); + return oid; + } + } - if (len != bufSize) - { - throw new LibGit2SharpException("Repository message file changed as we were reading it"); - } + public static unsafe ObjectId git_tag_create( + RepositoryHandle repo, + string name, + GitObject target, + Signature tagger, + string message, + bool allowOverwrite) + { + using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) + using (SignatureHandle sigHandle = tagger.BuildHandle()) + { + GitOid oid; + int res = NativeMethods.git_tag_create(out oid, repo, name, objectPtr.ObjectPtr, sigHandle, message, allowOverwrite); + Ensure.ZeroResult(res); - return Utf8Marshaler.Utf8FromBuffer(buf); + return oid; } } - public static ObjectDatabaseSafeHandle git_repository_odb(RepositorySafeHandle repo) + 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)) { - ObjectDatabaseSafeHandle handle; - int res = NativeMethods.git_repository_odb(out handle, repo); + GitOid oid; + int res = NativeMethods.git_tag_create_lightweight(out oid, repo, name, objectPtr.ObjectPtr, allowOverwrite); Ensure.ZeroResult(res); - return handle; + return oid; } } - public static RepositorySafeHandle git_repository_open(string path) + public static unsafe void git_tag_delete(RepositoryHandle repo, string name) { - using (ThreadAffinity()) - { - RepositorySafeHandle repo; - int res = NativeMethods.git_repository_open(out repo, path); + int res = NativeMethods.git_tag_delete(repo, name); + 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)); - } + public static unsafe IList git_tag_list(RepositoryHandle repo) + { + var array = new GitStrArrayNative(); + try + { + int res = NativeMethods.git_tag_list(out array.Array, repo); Ensure.ZeroResult(res); - return repo; + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static FilePath git_repository_path(RepositorySafeHandle repo) + public static unsafe string git_tag_message(ObjectHandle tag) { - return NativeMethods.git_repository_path(repo); + return NativeMethods.git_tag_message(tag); } - public static void git_repository_set_config(RepositorySafeHandle repo, ConfigurationSafeHandle config) + public static unsafe string git_tag_name(ObjectHandle tag) { - NativeMethods.git_repository_set_config(repo, config); + return NativeMethods.git_tag_name(tag); } - public static void git_repository_set_index(RepositorySafeHandle repo, IndexSafeHandle index) + public static unsafe Signature git_tag_tagger(ObjectHandle tag) { - NativeMethods.git_repository_set_index(repo, index); - } + git_signature* taggerHandle = NativeMethods.git_tag_tagger(tag); - public static void git_repository_set_workdir(RepositorySafeHandle repo, FilePath workdir) - { - using (ThreadAffinity()) + // Not all tags have a tagger signature - we need to handle + // this case. + Signature tagger = null; + if (taggerHandle != null) { - int res = NativeMethods.git_repository_set_workdir(repo, workdir, false); - Ensure.ZeroResult(res); + tagger = new Signature(taggerHandle); } + + return tagger; } - public static CurrentOperation git_repository_state(RepositorySafeHandle repo) + public static unsafe ObjectId git_tag_target_id(ObjectHandle tag) { - using (ThreadAffinity()) - { - int res = NativeMethods.git_repository_state(repo); - Ensure.Int32Result(res); - return (CurrentOperation)res; - } + return ObjectId.BuildFromPtr(NativeMethods.git_tag_target_id(tag)); } - public static FilePath git_repository_workdir(RepositorySafeHandle repo) + public static unsafe GitObjectType git_tag_target_type(ObjectHandle tag) { - return NativeMethods.git_repository_workdir(repo); + return NativeMethods.git_tag_target_type(tag); } #endregion - #region git_reset_ - - public static void git_reset( - RepositorySafeHandle repo, - ObjectId committishId, - ResetOptions resetKind) - { - using (ThreadAffinity()) - using (var osw = new ObjectSafeWrapper(committishId, repo)) - { - int res = NativeMethods.git_reset(repo, osw.ObjectPtr, resetKind); - Ensure.ZeroResult(res); - } + #region git_trace_ + + /// + /// 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) + { + int res = NativeMethods.git_trace_set(level, callback); + Ensure.ZeroResult(res); } #endregion - #region git_revparse_ + #region git_transport_ - public static GitObjectSafeHandle git_revparse_single(RepositorySafeHandle repo, string objectish) + public static void git_transport_register(string prefix, IntPtr transport_cb, IntPtr param) { - using (ThreadAffinity()) - { - GitObjectSafeHandle obj; - int res = NativeMethods.git_revparse_single(out obj, repo, objectish); + int res = NativeMethods.git_transport_register(prefix, transport_cb, param); - switch (res) - { - case (int)GitErrorCode.NotFound: - return null; + if (res == (int)GitErrorCode.Exists) + { + throw new EntryExistsException("A custom transport for '{0}' is already registered", + prefix); + } - case (int)GitErrorCode.Ambiguous: - throw new AmbiguousSpecificationException(string.Format(CultureInfo.InvariantCulture, "Provided abbreviated ObjectId '{0}' is too short.", objectish)); + Ensure.ZeroResult(res); + } - default: - Ensure.ZeroResult(res); - break; - } + public static void git_transport_unregister(string prefix) + { + int res = NativeMethods.git_transport_unregister(prefix); - return obj; + if (res == (int)GitErrorCode.NotFound) + { + throw new NotFoundException("The given transport was not found"); } + + Ensure.ZeroResult(res); } #endregion - #region git_revwalk_ + #region git_transport_smart_ - public static void git_revwalk_free(IntPtr walker) + public static int git_transport_smart_credentials(out IntPtr cred, IntPtr transport, string user, int methods) { - NativeMethods.git_revwalk_free(walker); + return NativeMethods.git_transport_smart_credentials(out cred, transport, user, methods); } - public static void git_revwalk_hide(RevWalkerSafeHandle walker, ObjectId commit_id) + #endregion + + #region git_tree_ + + public static unsafe Mode git_tree_entry_attributes(git_tree_entry* entry) { - using (ThreadAffinity()) - { - GitOid oid = commit_id.Oid; - int res = NativeMethods.git_revwalk_hide(walker, ref oid); - Ensure.ZeroResult(res); - } + return (Mode)NativeMethods.git_tree_entry_filemode(entry); } - public static RevWalkerSafeHandle git_revwalk_new(RepositorySafeHandle repo) + public static unsafe TreeEntryHandle git_tree_entry_byindex(ObjectHandle tree, long idx) { - using (ThreadAffinity()) + var handle = NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + if (handle == null) { - RevWalkerSafeHandle handle; - int res = NativeMethods.git_revwalk_new(out handle, repo); - Ensure.ZeroResult(res); - - return handle; + return null; } + + return new TreeEntryHandle(handle, false); } - public static ObjectId git_revwalk_next(RevWalkerSafeHandle walker) + public static unsafe TreeEntryHandle git_tree_entry_bypath(RepositoryHandle repo, ObjectId id, string treeentry_path) { - using (ThreadAffinity()) + using (var obj = new ObjectSafeWrapper(id, repo, throwIfMissing: true)) { - GitOid ret; - int res = NativeMethods.git_revwalk_next(out ret, walker); + git_tree_entry* treeEntryPtr; + int res = NativeMethods.git_tree_entry_bypath(out treeEntryPtr, obj.ObjectPtr, treeentry_path); - if (res == (int)GitErrorCode.IterOver) + if (res == (int)GitErrorCode.NotFound) { return null; } Ensure.ZeroResult(res); - return ret; + return new TreeEntryHandle(treeEntryPtr, true); } } - public static void git_revwalk_push(RevWalkerSafeHandle walker, ObjectId id) + public static unsafe ObjectId git_tree_entry_id(git_tree_entry* entry) { - using (ThreadAffinity()) - { - GitOid oid = id.Oid; - int res = NativeMethods.git_revwalk_push(walker, ref oid); - Ensure.ZeroResult(res); - } + return ObjectId.BuildFromPtr(NativeMethods.git_tree_entry_id(entry)); } - public static void git_revwalk_reset(RevWalkerSafeHandle walker) + public static unsafe string git_tree_entry_name(git_tree_entry* entry) { - NativeMethods.git_revwalk_reset(walker); + return NativeMethods.git_tree_entry_name(entry); } - public static void git_revwalk_sorting(RevWalkerSafeHandle walker, GitSortOptions options) + public static unsafe GitObjectType git_tree_entry_type(git_tree_entry* entry) { - NativeMethods.git_revwalk_sorting(walker, options); + return NativeMethods.git_tree_entry_type(entry); + } + + public static unsafe int git_tree_entrycount(ObjectHandle tree) + { + return (int)NativeMethods.git_tree_entrycount(tree); } #endregion - #region git_signature_ + #region git_treebuilder_ - public static void git_signature_free(IntPtr signature) + public static unsafe TreeBuilderHandle git_treebuilder_new(RepositoryHandle repo) { - NativeMethods.git_signature_free(signature); + git_treebuilder* builder; + int res = NativeMethods.git_treebuilder_new(out builder, repo, IntPtr.Zero); + Ensure.ZeroResult(res); + + return new TreeBuilderHandle(builder, true); } - public static SignatureSafeHandle git_signature_new(string name, string email, DateTimeOffset when) + public static unsafe void git_treebuilder_insert(TreeBuilderHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) { - using (ThreadAffinity()) - { - SignatureSafeHandle handle; - int res = NativeMethods.git_signature_new(out handle, name, email, when.ToSecondsSinceEpoch(), - (int)when.Offset.TotalMinutes); - Ensure.ZeroResult(res); - - return handle; - } + GitOid oid = treeEntryDefinition.TargetId.Oid; + int res = NativeMethods.git_treebuilder_insert(IntPtr.Zero, builder, treeentry_name, ref oid, + (uint)treeEntryDefinition.Mode); + Ensure.ZeroResult(res); } - #endregion - - #region git_stash_ - - public static ObjectId git_stash_save( - RepositorySafeHandle repo, - Signature stasher, - string prettifiedMessage, - StashOptions options) + public static unsafe ObjectId git_treebuilder_write(TreeBuilderHandle bld) { - using (ThreadAffinity()) - using (SignatureSafeHandle stasherHandle = stasher.BuildHandle()) - { - GitOid stashOid; + GitOid oid; + int res = NativeMethods.git_treebuilder_write(out oid, bld); + Ensure.ZeroResult(res); - int res = NativeMethods.git_stash_save(out stashOid, repo, stasherHandle, prettifiedMessage, options); + return oid; + } - if (res == (int)GitErrorCode.NotFound) - { - return null; - } + #endregion - Ensure.Int32Result(res); + #region git_transaction_ - return new ObjectId(stashOid); - } + public static void git_transaction_commit(IntPtr txn) + { + NativeMethods.git_transaction_commit(txn); } - public static ICollection git_stash_foreach( - RepositorySafeHandle repo, - Func resultSelector) + public static void git_transaction_free(IntPtr txn) { - 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); + NativeMethods.git_transaction_free(txn); } #endregion - #region git_status_ - - public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath path) - { - using (ThreadAffinity()) + #region git_libgit2_ + + /// + /// Returns the features with which libgit2 was compiled. + /// + public static BuiltInFeatures git_libgit2_features() + { + return (BuiltInFeatures)NativeMethods.git_libgit2_features(); + } + + // 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 + } + + /// + /// 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) + { + string path; + + using (var buf = new GitBuf()) { - FileStatus status; - int res = NativeMethods.git_status_file(out status, repo, path); - - switch (res) - { - case (int)GitErrorCode.NotFound: - return FileStatus.Nonexistent; - - 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)); - - default: - Ensure.ZeroResult(res); - break; - } + 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 status; + path = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; } + + return path; } - public static ICollection git_status_foreach(RepositorySafeHandle repo, Func resultSelector) + public static void git_libgit2_opts_enable_strict_hash_verification(bool enabled) { - return git_foreach(resultSelector, c => NativeMethods.git_status_foreach(repo, (x, y, p) => c(x, y, p), IntPtr.Zero)); + 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); + } + + /// + /// 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); } - #endregion + /// + /// 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); + } - #region git_tag_ + /// + /// 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) + { + // 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); + } - public static ObjectId git_tag_create( - RepositorySafeHandle repo, - string name, - GitObject target, - Signature tagger, - string message, - bool allowOverwrite) + /// + /// 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) { - using (ThreadAffinity()) - using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) - using (SignatureSafeHandle taggerHandle = tagger.BuildHandle()) - { - GitOid oid; - int res = NativeMethods.git_tag_create(out oid, repo, name, objectPtr.ObjectPtr, taggerHandle, message, allowOverwrite); - Ensure.ZeroResult(res); + // 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); + } - return oid; - } + /// + /// 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); } - public static ObjectId git_tag_create_lightweight(RepositorySafeHandle repo, string name, GitObject target, bool allowOverwrite) + /// + /// Gets the user-agent string used by libgit2. + /// + /// The user-agent string. + /// + /// + public static string git_libgit2_opts_get_user_agent() { - using (ThreadAffinity()) - using (var objectPtr = new ObjectSafeWrapper(target.Id, repo)) + string userAgent; + + using (var buf = new GitBuf()) { - GitOid oid; - int res = NativeMethods.git_tag_create_lightweight(out oid, repo, name, objectPtr.ObjectPtr, allowOverwrite); + 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 oid; + userAgent = LaxUtf8Marshaler.FromNative(buf.ptr) ?? string.Empty; } + + return userAgent; } - public static void git_tag_delete(RepositorySafeHandle repo, string name) + public static void git_libgit2_opts_set_extensions(string[] extensions) { - using (ThreadAffinity()) + using (var array = GitStrArrayManaged.BuildFrom(extensions)) { - int res = NativeMethods.git_tag_delete(repo, name); + 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); } } - public static IList git_tag_list(RepositorySafeHandle repo) + public static string[] git_libgit2_opts_get_extensions() { - using (ThreadAffinity()) + var array = new GitStrArrayNative(); + + try { - UnSafeNativeMethods.git_strarray arr; - int res = UnSafeNativeMethods.git_tag_list(out arr, repo); + 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 Libgit2UnsafeHelper.BuildListOf(arr); + return array.ReadStrings(); + } + finally + { + array.Dispose(); } } - public static string git_tag_message(GitObjectSafeHandle tag) + /// + /// Gets the value of owner validation + /// + public static unsafe bool git_libgit2_opts_get_owner_validation() { - return NativeMethods.git_tag_message(tag); - } + int res; + int enabled; - public static string git_tag_name(GitObjectSafeHandle tag) - { - return NativeMethods.git_tag_name(tag); - } + 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); + } - public static Signature git_tag_tagger(GitObjectSafeHandle tag) - { - return new Signature(NativeMethods.git_tag_tagger(tag)); + Ensure.ZeroResult(res); + + return enabled != 0; } - public static ObjectId git_tag_target_oid(GitObjectSafeHandle tag) + /// + /// Enable or disable owner validation + /// + /// true to enable owner validation, false otherwise + public static void git_libgit2_opts_set_owner_validation(bool enabled) { - return NativeMethods.git_tag_target_id(tag).MarshalAsObjectId(); + int res; + + if (isOSXArm64) + { + 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); + } + + Ensure.ZeroResult(res); } + #endregion - public static GitObjectType git_tag_target_type(GitObjectSafeHandle tag) + #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) { - return NativeMethods.git_tag_target_type(tag); - } + git_worktree* worktree; + var res = NativeMethods.git_worktree_lookup(out worktree, repo, name); - #endregion + switch (res) + { + case (int)GitErrorCode.Error: + case (int)GitErrorCode.NotFound: + case (int)GitErrorCode.Exists: + case (int)GitErrorCode.OrphanedHead: + return null; - #region git_tree_ + default: + Ensure.ZeroResult(res); + return new WorktreeHandle(worktree, true); + } + } - public static Mode git_tree_entry_attributes(SafeHandle entry) + public static unsafe IList git_worktree_list(RepositoryHandle repo) { - return (Mode)NativeMethods.git_tree_entry_filemode(entry); + var array = new GitStrArrayNative(); + + try + { + int res = NativeMethods.git_worktree_list(out array.Array, repo); + Ensure.ZeroResult(res); + + return array.ReadStrings(); + } + finally + { + array.Dispose(); + } } - public static TreeEntrySafeHandle git_tree_entry_byindex(GitObjectSafeHandle tree, long idx) + public static unsafe RepositoryHandle git_repository_open_from_worktree(WorktreeHandle handle) { - return NativeMethods.git_tree_entry_byindex(tree, (UIntPtr)idx); + git_repository* repo; + int res = NativeMethods.git_repository_open_from_worktree(out repo, handle); + + 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); } - public static TreeEntrySafeHandle_Owned git_tree_entry_bypath(RepositorySafeHandle repo, ObjectId id, FilePath treeentry_path) + public static unsafe WorktreeLock git_worktree_is_locked(WorktreeHandle worktree) { - using (ThreadAffinity()) - using (var obj = new ObjectSafeWrapper(id, repo)) + using (var buf = new GitBuf()) { - TreeEntrySafeHandle_Owned treeEntryPtr; - int res = NativeMethods.git_tree_entry_bypath(out treeEntryPtr, obj.ObjectPtr, treeentry_path); + int res = NativeMethods.git_worktree_is_locked(buf, worktree); - if (res == (int)GitErrorCode.NotFound) + if (res < 0) { + // error return null; } - Ensure.ZeroResult(res); + if (res == (int)GitErrorCode.Ok) + { + return new WorktreeLock(); + } - return treeEntryPtr; + return new WorktreeLock(true, LaxUtf8Marshaler.FromNative(buf.ptr)); } } - public static void git_tree_entry_free(IntPtr treeEntry) + public static unsafe bool git_worktree_validate(WorktreeHandle worktree) { - NativeMethods.git_tree_entry_free(treeEntry); - } - - public static ObjectId git_tree_entry_id(SafeHandle entry) - { - return NativeMethods.git_tree_entry_id(entry).MarshalAsObjectId(); - } + int res = NativeMethods.git_worktree_validate(worktree); - public static string git_tree_entry_name(SafeHandle entry) - { - return NativeMethods.git_tree_entry_name(entry); + return res == (int)GitErrorCode.Ok; } - public static GitObjectType git_tree_entry_type(SafeHandle entry) + public static unsafe bool git_worktree_unlock(WorktreeHandle worktree) { - return NativeMethods.git_tree_entry_type(entry); - } + int res = NativeMethods.git_worktree_unlock(worktree); - public static int git_tree_entrycount(GitObjectSafeHandle tree) - { - return (int)NativeMethods.git_tree_entrycount(tree); + return res == (int)GitErrorCode.Ok; } - #endregion - - #region git_treebuilder_ - - public static TreeBuilderSafeHandle git_treebuilder_create() + public static unsafe bool git_worktree_lock(WorktreeHandle worktree, string reason) { - using (ThreadAffinity()) - { - TreeBuilderSafeHandle builder; - int res = NativeMethods.git_treebuilder_create(out builder, IntPtr.Zero); - Ensure.ZeroResult(res); + int res = NativeMethods.git_worktree_lock(worktree, reason); - return builder; - } - } - - public static void git_treebuilder_free(IntPtr bld) - { - NativeMethods.git_treebuilder_free(bld); + return res == (int)GitErrorCode.Ok; } - public static void git_treebuilder_insert(TreeBuilderSafeHandle builder, string treeentry_name, TreeEntryDefinition treeEntryDefinition) + public static unsafe WorktreeHandle git_worktree_add( + RepositoryHandle repo, + string name, + string path, + git_worktree_add_options options) { - 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); - } + git_worktree* worktree; + int res = NativeMethods.git_worktree_add(out worktree, repo, name, path, options); + Ensure.ZeroResult(res); + return new WorktreeHandle(worktree, true); } - public static ObjectId git_treebuilder_write(RepositorySafeHandle repo, TreeBuilderSafeHandle bld) + public static unsafe bool git_worktree_prune(WorktreeHandle worktree, + git_worktree_prune_options options) { - using (ThreadAffinity()) - { - GitOid oid; - int res = NativeMethods.git_treebuilder_write(out oid, repo, bld); - Ensure.ZeroResult(res); - - return oid; - } + int res = NativeMethods.git_worktree_prune(worktree, options); + Ensure.ZeroResult(res); + return true; } #endregion @@ -2008,23 +3741,20 @@ private static ICollection git_foreach( Func, int> iterator, params GitErrorCode[] ignoredErrorCodes) { - using (ThreadAffinity()) + var result = new List(); + var res = iterator((x, payload) => { - var result = new List(); - var res = iterator((x, payload) => - { - result.Add(resultSelector(x)); - return 0; - }); + result.Add(resultSelector(x)); + return 0; + }); - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } - - Ensure.ZeroResult(res); - return result; + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) + { + return Array.Empty(); } + + Ensure.ZeroResult(res); + return result; } private static ICollection git_foreach( @@ -2032,23 +3762,20 @@ private static ICollection git_foreach( Func, int> iterator, params GitErrorCode[] ignoredErrorCodes) { - using (ThreadAffinity()) + var result = new List(); + var res = iterator((x, y, payload) => { - 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 new TResult[0]; - } + result.Add(resultSelector(x, y)); + return 0; + }); - Ensure.ZeroResult(res); - return result; + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) + { + return Array.Empty(); } + + Ensure.ZeroResult(res); + return result; } private static ICollection git_foreach( @@ -2056,23 +3783,20 @@ private static ICollection git_foreach( Func, int> iterator, params GitErrorCode[] ignoredErrorCodes) { - using (ThreadAffinity()) + var result = new List(); + var res = iterator((w, x, y, payload) => { - var result = new List(); - var res = iterator((w, x, y, payload) => - { - result.Add(resultSelector(w, x, y)); - return 0; - }); + result.Add(resultSelector(w, x, y)); + return 0; + }); - if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) - { - return new TResult[0]; - } - - Ensure.ZeroResult(res); - return result; + 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); @@ -2082,70 +3806,35 @@ private static ICollection git_foreach( Func, int> iterator, params GitErrorCode[] ignoredErrorCodes) { - using (ThreadAffinity()) + var result = new List(); + var res = iterator((w, x, y, z, payload) => { - 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)) - { - return new TResult[0]; - } + result.Add(resultSelector(w, x, y, z)); + return 0; + }); - Ensure.ZeroResult(res); - return result; - } - } - - private static unsafe class Libgit2UnsafeHelper - { - public static IList BuildListOf(UnSafeNativeMethods.git_strarray strArray) + if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res)) { - - try - { - UnSafeNativeMethods.git_strarray* gitStrArray = &strArray; - - var numberOfEntries = (int)gitStrArray->size; - var list = new List(numberOfEntries); - for (uint i = 0; i < numberOfEntries; i++) - { - var name = Utf8Marshaler.FromNative((IntPtr)gitStrArray->strings[i]); - list.Add(name); - } - - list.Sort(StringComparer.Ordinal); - return list; - } - finally - { - UnSafeNativeMethods.git_strarray_free(ref strArray); - } + return Array.Empty(); } + + Ensure.ZeroResult(res); + return result; } - private static bool RepositoryStateChecker(RepositorySafeHandle repo, Func checker) + private static unsafe bool RepositoryStateChecker(RepositoryHandle repo, Func checker) { - using (ThreadAffinity()) - { - int res = checker(repo); - Ensure.BooleanResult(res); + int res = checker(repo.AsIntPtr()); + Ensure.BooleanResult(res); - return (res == 1); - } + return (res == 1); } - private static string ConvertPath(Func pathRetriever) + private static FilePath ConvertPath(Func pathRetriever) { - using (ThreadAffinity()) + using (var buf = new GitBuf()) { - var buffer = new byte[NativeMethods.GIT_PATH_MAX]; - - int result = pathRetriever(buffer, (UIntPtr)NativeMethods.GIT_PATH_MAX); + int result = pathRetriever(buf); if (result == (int)GitErrorCode.NotFound) { @@ -2153,48 +3842,71 @@ private static string ConvertPath(Func pathRetriever) } Ensure.ZeroResult(result); - - return Utf8Marshaler.Utf8FromBuffer(buffer); + return LaxFilePathMarshaler.FromNative(buf.ptr); } } - private static Func ThreadAffinity = WithoutThreadAffinity; - - internal static void EnableThreadAffinity() + private static readonly IDictionary> configurationParser = new Dictionary> { - ThreadAffinity = WithThreadAffinity; - } + { typeof(int), value => git_config_parse_int32(value) }, + { typeof(long), value => git_config_parse_int64(value) }, + { typeof(bool), value => git_config_parse_bool(value) }, + { typeof(string), value => value }, + }; - private static IDisposable WithoutThreadAffinity() + /// + /// Helper method for consistent conversion of return value on + /// Callbacks that support cancellation from bool to native type. + /// True indicates that function should continue, false indicates + /// user wants to cancel. + /// + /// + /// + internal static int ConvertResultToCancelFlag(bool result) { - return null; + return result ? 0 : (int)GitErrorCode.User; } + } - private static IDisposable WithThreadAffinity() + /// + /// 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) { - return new DisposableThreadAffinityWrapper(); + ulong ulongValue = (ulong)input; + if (ulongValue > int.MaxValue) + { + throw new LibGit2SharpException("value exceeds size of an int"); + } + + return (int)input; } - private class DisposableThreadAffinityWrapper : IDisposable + + /// + /// Convert a UIntPtr to a long value. Will throw + /// exception if there is an overflow. + /// + /// + /// + public static long ConvertToLong(this UIntPtr input) { - public DisposableThreadAffinityWrapper() + ulong ulongValue = (ulong)input; + if (ulongValue > long.MaxValue) { - Thread.BeginThreadAffinity(); + throw new LibGit2SharpException("value exceeds size of long"); } - public void Dispose() - { - Thread.EndThreadAffinity(); - } + return (long)input; } - - private static readonly IDictionary> configurationParser = new Dictionary> - { - { typeof(int), value => git_config_parse_int32(value) }, - { typeof(long), value => git_config_parse_int64(value) }, - { typeof(bool), value => git_config_parse_bool(value) }, - { typeof(string), value => value }, - }; } } // ReSharper restore InconsistentNaming diff --git a/LibGit2Sharp/Core/PushTransferProgressCallbacks.cs b/LibGit2Sharp/Core/PushTransferProgressCallbacks.cs new file mode 100644 index 000000000..2d40bf687 --- /dev/null +++ b/LibGit2Sharp/Core/PushTransferProgressCallbacks.cs @@ -0,0 +1,38 @@ +using System; +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp.Core +{ + internal class PushTransferCallbacks + { + private readonly PushTransferProgressHandler onPushTransferProgress; + + /// + /// Constructor to set up the native callback given managed delegate. + /// + /// The delegate that the git_transfer_progress_callback will call. + internal PushTransferCallbacks(PushTransferProgressHandler onPushTransferProgress) + { + this.onPushTransferProgress = onPushTransferProgress; + } + + /// + /// Generates a delegate that matches the native git_transfer_progress_callback function's signature and wraps the delegate. + /// + /// A delegate method with a signature that matches git_transfer_progress_callback. + internal NativeMethods.git_push_transfer_progress GenerateCallback() + { + if (onPushTransferProgress == null) + { + return null; + } + + return new PushTransferCallbacks(onPushTransferProgress).OnGitTransferProgress; + } + + private int OnGitTransferProgress(uint current, uint total, UIntPtr bytes, IntPtr payload) + { + return Proxy.ConvertResultToCancelFlag(onPushTransferProgress((int)current, (int)total, (long)bytes)); + } + } +} diff --git a/LibGit2Sharp/Core/RawContentStream.cs b/LibGit2Sharp/Core/RawContentStream.cs index 52d7ab789..92b4b3bf0 100644 --- a/LibGit2Sharp/Core/RawContentStream.cs +++ b/LibGit2Sharp/Core/RawContentStream.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using LibGit2Sharp.Core.Handles; @@ -6,26 +7,62 @@ namespace LibGit2Sharp.Core { internal class RawContentStream : UnmanagedMemoryStream { - private readonly ObjectSafeWrapper wrapper; + private readonly ObjectHandle handle; + private readonly ICollection linkedResources; - internal RawContentStream(ObjectId id, RepositorySafeHandle repo, - Func bytePtrProvider, long length) - : this(new ObjectSafeWrapper(id, repo), bytePtrProvider, length) + internal unsafe RawContentStream( + ObjectHandle handle, + Func bytePtrProvider, + Func sizeProvider, + ICollection linkedResources = null) + : base((byte*)Wrap(handle, bytePtrProvider, linkedResources).ToPointer(), + Wrap(handle, sizeProvider, linkedResources)) { + this.handle = handle; + this.linkedResources = linkedResources; } - unsafe RawContentStream(ObjectSafeWrapper wrapper, - Func bytePtrProvider, long length) - : base((byte*)bytePtrProvider(wrapper.ObjectPtr).ToPointer(), length) + private static T Wrap( + ObjectHandle handle, + Func provider, + IEnumerable linkedResources) { - this.wrapper = wrapper; + T value; + + try + { + value = provider(handle); + } + catch + { + Dispose(handle, linkedResources); + throw; + } + + return value; + } + + private static void Dispose( + ObjectHandle handle, + IEnumerable linkedResources) + { + handle.SafeDispose(); + + if (linkedResources == null) + { + return; + } + + foreach (IDisposable linkedResource in linkedResources) + { + linkedResource.Dispose(); + } } protected override void Dispose(bool disposing) { base.Dispose(disposing); - wrapper.SafeDispose(); + Dispose(handle, linkedResources); } } } - diff --git a/LibGit2Sharp/Core/RepositoryOpenFlags.cs b/LibGit2Sharp/Core/RepositoryOpenFlags.cs new file mode 100644 index 000000000..e0e6a31bb --- /dev/null +++ b/LibGit2Sharp/Core/RepositoryOpenFlags.cs @@ -0,0 +1,27 @@ +using System; + +namespace LibGit2Sharp.Core +{ + /// + /// Option flags for `git_repository_open_ext` + /// + [Flags] + internal enum RepositoryOpenFlags + { + /// + /// Only open the repository if it can be + /// * immediately found in the start_path. Do not walk up from the + /// * start_path looking at parent directories. + /// + NoSearch = (1 << 0), /* GIT_REPOSITORY_OPEN_NO_SEARCH */ + + /// + /// Unless this flag is set, open will not + /// * continue searching across filesystem boundaries (i.e. when `st_dev` + /// * changes from the `stat` system call). (E.g. Searching in a user's home + /// * directory "/home/user/source/" will not return "/.git/" as the found + /// * repo if "/" is a different filesystem than "/home".) + /// + CrossFS = (1 << 1), /* GIT_REPOSITORY_OPEN_CROSS_FS */ + } +} diff --git a/LibGit2Sharp/Core/StringExtensions.cs b/LibGit2Sharp/Core/StringExtensions.cs new file mode 100644 index 000000000..2d557e23f --- /dev/null +++ b/LibGit2Sharp/Core/StringExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal static class StringExtensions + { + public static int OctalToInt32(this string octal) + { + return Convert.ToInt32(octal, 8); + } + } +} diff --git a/LibGit2Sharp/Core/SubmoduleLazyGroup.cs b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs new file mode 100644 index 000000000..42e40e07b --- /dev/null +++ b/LibGit2Sharp/Core/SubmoduleLazyGroup.cs @@ -0,0 +1,27 @@ +using System; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp.Core +{ + internal class SubmoduleLazyGroup : LazyGroup + { + private readonly string name; + + public SubmoduleLazyGroup(Repository repo, string name) + : base(repo) + { + this.name = name; + } + + protected override void EvaluateInternal(Action evaluator) + { + repo.Submodules.Lookup(name, + handle => + { + evaluator(handle); + return default(object); + }, + true); + } + } +} diff --git a/LibGit2Sharp/Core/TarWriter.cs b/LibGit2Sharp/Core/TarWriter.cs new file mode 100644 index 000000000..0a051b9e6 --- /dev/null +++ b/LibGit2Sharp/Core/TarWriter.cs @@ -0,0 +1,502 @@ +/* + * Source: http://code.google.com/p/tar-cs/ + * + * BSD License + * + * Copyright (c) 2009, Vladimir Vasiltsov + * 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. + * * Names of its contributors may not 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. + */ + +using System; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; + +namespace LibGit2Sharp.Core +{ + internal class TarWriter : IDisposable + { + private readonly Stream outStream; + + /// + /// Writes tar (see GNU tar) archive to a stream + /// + /// stream to write archive to + public TarWriter(Stream writeStream) + { + outStream = writeStream; + } + + protected Stream OutStream + { + get { return outStream; } + } + + #region IDisposable Members + + public void Dispose() + { + AlignTo512(0, true); + AlignTo512(0, true); + + GC.SuppressFinalize(this); + } + + #endregion + + public void Write( + FilePath filePath, + Stream data, + DateTimeOffset modificationTime, + int mode, + string userId, + string groupId, + char typeflag, + string userName, + string groupName, + string deviceMajorNumber, + string deviceMinorNumber, + string entrySha, + bool isLink) + { + FileNameExtendedHeader fileNameExtendedHeader = FileNameExtendedHeader.Parse(filePath.Posix, entrySha); + LinkExtendedHeader linkExtendedHeader = ParseLink(isLink, data, entrySha); + + 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); + + // folders have no data, and so do links + if (data != null && !isLink) + { + WriteContent(data.Length, data, OutStream); + } + AlignTo512((data != null) ? data.Length : 0, false); + } + + protected static void WriteContent(long count, Stream data, Stream dest) + { + var buffer = new byte[1024]; + + while (count > 0 && count > buffer.Length) + { + 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; + } + if (count > 0) + { + 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) + { + dest.WriteByte(0); + --count; + } + } + else + { + dest.Write(buffer, 0, bytesRead); + } + } + } + + protected void AlignTo512(long size, bool acceptZero) + { + size = size % 512; + if (size == 0 && !acceptZero) return; + while (size < 512) + { + OutStream.WriteByte(0); + size++; + } + } + + protected void WriteHeader( + string fileName, + string namePrefix, + DateTimeOffset lastModificationTime, + long count, + int mode, + string userId, + string groupId, + char typeflag, + string link, + string userName, + string groupName, + string deviceMajorNumber, + string 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); + } + + private static LinkExtendedHeader ParseLink(bool isLink, Stream data, string entrySha) + { + if (!isLink) + { + return new LinkExtendedHeader(string.Empty, string.Empty, false); + } + + using (var dest = new MemoryStream()) + { + WriteContent(data.Length, data, dest); + dest.Seek(0, SeekOrigin.Begin); + + using (var linkStream = new StreamReader(dest)) + { + string link = linkStream.ReadToEnd(); + + if (data.Length > 100) + { + return new LinkExtendedHeader(link, + string.Format(CultureInfo.InvariantCulture, + "see %s.paxheader{0}", + entrySha), + true); + } + + return new LinkExtendedHeader(link, link, false); + } + } + } + + private void WriteExtendedHeader(FileNameExtendedHeader fileNameExtendedHeader, LinkExtendedHeader linkExtendedHeader, string entrySha, + DateTimeOffset modificationTime) + { + string extHeader = string.Empty; + + if (fileNameExtendedHeader.NeedsExtendedHeaderEntry) + { + extHeader += BuildKeyValueExtHeader("path", fileNameExtendedHeader.InitialPath); + } + + if (linkExtendedHeader.NeedsExtendedHeaderEntry) + { + extHeader += BuildKeyValueExtHeader("linkpath", linkExtendedHeader.InitialLink); + } + + if (string.IsNullOrEmpty(extHeader)) + { + return; + } + + 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); + } + } + + 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); + } + + /// + /// UsTar header implementation. + /// + private class UsTarHeader + { + private readonly string mode; + private readonly long size; + private readonly string unixTime; + private const string magic = "ustar"; + private const string version = "00"; + private readonly string userName; + private readonly string groupName; + private readonly string userId; + private readonly string groupId; + private readonly char typeflag; + private readonly string link; + private readonly string deviceMajorNumber; + private readonly string deviceMinorNumber; + private readonly string namePrefix; + private readonly string fileName; + + public UsTarHeader( + string fileName, + string namePrefix, + DateTimeOffset lastModificationTime, + long size, + int mode, + string userId, + string groupId, + char typeflag, + string link, + string userName, + string groupName, + string deviceMajorNumber, + string deviceMinorNumber) + { + #region Length validations + + if (userName.Length > 32) + { + 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.", nameof(groupName)); + } + if (userId.Length > 7) + { + 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.", nameof(groupId)); + } + if (deviceMajorNumber.Length > 7) + { + 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.", nameof(deviceMinorNumber)); + } + if (link.Length > 100) + { + 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.ToUnixTimeSeconds(), 8).PadLeft(11, '0'); + this.userId = userId.PadLeft(7, '0'); + this.groupId = userId.PadLeft(7, '0'); + this.userName = userName; + this.groupName = groupName; + this.typeflag = typeflag; + this.link = link; + this.deviceMajorNumber = deviceMajorNumber.PadLeft(7, '0'); + this.deviceMinorNumber = deviceMinorNumber.PadLeft(7, '0'); + + this.fileName = fileName; + this.namePrefix = namePrefix; + } + + public byte[] GetHeaderValue() + { + var buffer = new byte[512]; + + // Fill header + Encoding.ASCII.GetBytes(fileName.PadRight(100, '\0')).CopyTo(buffer, 0); + Encoding.ASCII.GetBytes(mode).CopyTo(buffer, 100); + Encoding.ASCII.GetBytes(userId).CopyTo(buffer, 108); + Encoding.ASCII.GetBytes(groupId).CopyTo(buffer, 116); + Encoding.ASCII.GetBytes(Convert.ToString(size, 8).PadLeft(11, '0')).CopyTo(buffer, 124); + Encoding.ASCII.GetBytes(unixTime).CopyTo(buffer, 136); + buffer[156] = Convert.ToByte(typeflag); + Encoding.ASCII.GetBytes(link).CopyTo(buffer, 157); + + Encoding.ASCII.GetBytes(magic).CopyTo(buffer, 257); // Mark header as ustar + Encoding.ASCII.GetBytes(version).CopyTo(buffer, 263); + Encoding.ASCII.GetBytes(userName).CopyTo(buffer, 265); + Encoding.ASCII.GetBytes(groupName).CopyTo(buffer, 297); + Encoding.ASCII.GetBytes(deviceMajorNumber).CopyTo(buffer, 329); + Encoding.ASCII.GetBytes(deviceMinorNumber).CopyTo(buffer, 337); + Encoding.ASCII.GetBytes(namePrefix).CopyTo(buffer, 345); + + if (size >= 0x1FFFFFFFF) + { + byte[] bytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(size)); + SetMarker(AlignTo12(bytes)).CopyTo(buffer, 124); + } + + string checksum = CalculateChecksum(buffer); + Encoding.ASCII.GetBytes(checksum).CopyTo(buffer, 148); + Encoding.ASCII.GetBytes("\0").CopyTo(buffer, 155); + + return buffer; + } + + private static string CalculateChecksum(byte[] buf) + { + Encoding.ASCII.GetBytes(new string(' ', 8)).CopyTo(buf, 148); + + long headerChecksum = buf.Aggregate(0, (current, b) => current + b); + + return Convert.ToString(headerChecksum, 8).PadLeft(7, '0'); + } + + private static byte[] SetMarker(byte[] bytes) + { + bytes[0] |= 0x80; + return bytes; + } + + private static byte[] AlignTo12(byte[] bytes) + { + var retVal = new byte[12]; + bytes.CopyTo(retVal, 12 - bytes.Length); + return retVal; + } + } + + private class FileNameExtendedHeader + { + private readonly string prefix; + private readonly string name; + private readonly string initialPath; + private readonly bool needsExtendedHeaderEntry; + + private FileNameExtendedHeader(string initialPath, string prefix, string name, bool needsExtendedHeaderEntry) + { + this.initialPath = initialPath; + this.prefix = prefix; + this.name = name; + this.needsExtendedHeaderEntry = needsExtendedHeaderEntry; + } + + public bool NeedsExtendedHeaderEntry + { + get { return needsExtendedHeaderEntry; } + } + + public string Name + { + get { return name; } + } + + public string Prefix + { + get { return prefix; } + } + + public string InitialPath + { + get { return initialPath; } + } + + /// + /// Logic taken from https://github.com/git/git/blob/master/archive-tar.c + /// + public static FileNameExtendedHeader Parse(string posixPath, string entrySha) + { + if (posixPath.Length > 100) + { + // Need to increment by one because while loop decrements first before testing for path separator + int position = Math.Min(156, posixPath.Length); + + while (--position > 0 && !Equals('/', posixPath[position])) + { } + + int remaining = posixPath.Length - position - 1; + if (remaining < 100 && position > 0) + { + 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, posixPath, false); + } + } + + private class LinkExtendedHeader + { + private readonly string initialLink; + private readonly string link; + private readonly bool needsExtendedHeaderEntry; + + public LinkExtendedHeader(string initialLink, string link, bool needsExtendedHeaderEntry) + { + this.initialLink = initialLink; + this.link = link; + this.needsExtendedHeaderEntry = needsExtendedHeaderEntry; + } + + public string InitialLink + { + get { return initialLink; } + } + + public bool NeedsExtendedHeaderEntry + { + get { return needsExtendedHeaderEntry; } + } + + public string Link + { + get { return link; } + } + } + } +} diff --git a/LibGit2Sharp/Core/UnSafeNativeMethods.cs b/LibGit2Sharp/Core/UnSafeNativeMethods.cs deleted file mode 100644 index 49093e6e3..000000000 --- a/LibGit2Sharp/Core/UnSafeNativeMethods.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Runtime.InteropServices; -using LibGit2Sharp.Core.Handles; - -namespace LibGit2Sharp.Core -{ - internal static unsafe class UnSafeNativeMethods - { - private const string libgit2 = "git2"; - - [DllImport(libgit2)] - internal static extern int git_reference_list(out git_strarray array, RepositorySafeHandle repo, GitReferenceType flags); - - [DllImport(libgit2)] - internal static extern int git_remote_list(out git_strarray array, RepositorySafeHandle repo); - - [DllImport(libgit2)] - internal static extern int git_tag_list(out git_strarray array, RepositorySafeHandle repo); - - [DllImport(libgit2)] - internal static extern void git_strarray_free(ref git_strarray array); - - #region Nested type: git_strarray - - internal struct git_strarray - { - public sbyte** strings; - public UIntPtr size; - } - - #endregion - } -} diff --git a/LibGit2Sharp/Core/Utf8Marshaler.cs b/LibGit2Sharp/Core/Utf8Marshaler.cs index ec78db8fc..54e0086cb 100644 --- a/LibGit2Sharp/Core/Utf8Marshaler.cs +++ b/LibGit2Sharp/Core/Utf8Marshaler.cs @@ -1,23 +1,25 @@ using System; +using System.Globalization; using System.Runtime.InteropServices; using System.Text; namespace LibGit2Sharp.Core { /// - /// This marshaler is to be used for capturing a UTF-8 string owned by libgit2 and - /// converting it to a managed String instance. The marshaler will not attempt to - /// free the native pointer after conversion, because the memory is owned by libgit2. + /// This marshaler is to be used for capturing a UTF-8 string owned by libgit2 and + /// converting it to a managed String instance. The marshaler will not attempt to + /// free the native pointer after conversion, because the memory is owned by libgit2. /// - /// Use this marshaler for return values, for example: - /// [return: MarshalAs(UnmanagedType.CustomMarshaler, - /// MarshalTypeRef = typeof(Utf8NoCleanupMarshaler))] + /// Use this marshaler for return values, for example: + /// [return: MarshalAs(UnmanagedType.CustomMarshaler, + /// MarshalCookie = UniqueId.UniqueIdentifier, + /// MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] /// - internal class Utf8NoCleanupMarshaler : Utf8Marshaler + internal class LaxUtf8NoCleanupMarshaler : LaxUtf8Marshaler { - private static readonly Utf8NoCleanupMarshaler staticInstance = new Utf8NoCleanupMarshaler(); + private static readonly LaxUtf8NoCleanupMarshaler staticInstance = new LaxUtf8NoCleanupMarshaler(); - public new static ICustomMarshaler GetInstance(String cookie) + public new static ICustomMarshaler GetInstance(string cookie) { return staticInstance; } @@ -25,162 +27,114 @@ internal class Utf8NoCleanupMarshaler : Utf8Marshaler #region ICustomMarshaler public override void CleanUpNativeData(IntPtr pNativeData) - { - } + { } #endregion } /// - /// This marshaler is to be used for sending managed String instances to libgit2. - /// The marshaler will allocate a buffer in native memory to hold the UTF-8 string - /// and perform the encoding conversion using that buffer as the target. The pointer - /// received by libgit2 will be to this buffer. After the function call completes, the - /// native buffer is freed. + /// This marshaler is to be used for sending managed String instances to libgit2. + /// The marshaler will allocate a buffer in native memory to hold the UTF-8 string + /// and perform the encoding conversion using that buffer as the target. The pointer + /// received by libgit2 will be to this buffer. After the function call completes, the + /// native buffer is freed. /// - /// Use this marshaler for function parameters, for example: - /// [DllImport(libgit2)] - /// internal static extern int git_tag_delete(RepositorySafeHandle repo, - /// [MarshalAs(UnmanagedType.CustomMarshaler, - /// MarshalTypeRef = typeof(Utf8Marshaler))] String tagName); + /// Use this marshaler for function parameters, for example: + /// [DllImport(libgit2)] + /// internal static extern int git_tag_delete(RepositorySafeHandle repo, + /// [MarshalAs(UnmanagedType.CustomMarshaler + /// MarshalCookie = UniqueId.UniqueIdentifier, + /// MarshalTypeRef = typeof(StrictUtf8Marshaler))] String tagName); /// - internal class Utf8Marshaler : ICustomMarshaler + internal class StrictUtf8Marshaler : EncodingMarshaler { - private static readonly Utf8Marshaler staticInstance = new Utf8Marshaler(); + private static readonly StrictUtf8Marshaler staticInstance; + private static readonly Encoding encoding; - public static ICustomMarshaler GetInstance(String cookie) + static StrictUtf8Marshaler() { - return staticInstance; + encoding = new UTF8Encoding(false, true); + staticInstance = new StrictUtf8Marshaler(); } - #region ICustomMarshaler + public StrictUtf8Marshaler() : base(encoding) + { } - public void CleanUpManagedData(Object managedObj) + public static ICustomMarshaler GetInstance(string cookie) { + return staticInstance; } - public virtual void CleanUpNativeData(IntPtr pNativeData) + #region ICustomMarshaler + + public override object MarshalNativeToManaged(IntPtr pNativeData) { - if (IntPtr.Zero != pNativeData) - { - Marshal.FreeHGlobal(pNativeData); - } + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to retrieve data from libgit2.", + GetType().Name)); } - public int GetNativeDataSize() + #endregion + + public static IntPtr FromManaged(string value) { - // Not a value type - return -1; + return FromManaged(encoding, value); } + } - public IntPtr MarshalManagedToNative(Object managedObj) - { - if (null == managedObj) - { - return IntPtr.Zero; - } + /// + /// This marshaler is to be used for capturing a UTF-8 string allocated by libgit2 and + /// converting it to a managed String instance. The marshaler will free the native pointer + /// after conversion. + /// + internal class LaxUtf8Marshaler : EncodingMarshaler + { + private static readonly LaxUtf8Marshaler staticInstance = new LaxUtf8Marshaler(); - String str = managedObj as String; + public static readonly Encoding Encoding = new UTF8Encoding(false, false); - if (null == str) - { - throw new MarshalDirectiveException("Utf8Marshaler must be used on a string."); - } + public LaxUtf8Marshaler() : base(Encoding) + { } - return FromManaged(str); + public static ICustomMarshaler GetInstance(string cookie) + { + return staticInstance; } - public Object MarshalNativeToManaged(IntPtr pNativeData) + #region ICustomMarshaler + + public override IntPtr MarshalManagedToNative(object managedObj) { - return FromNative(pNativeData); + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "{0} cannot be used to pass data to libgit2.", + GetType().Name)); } #endregion - public static unsafe IntPtr FromManaged(String value) + public static unsafe string FromNative(char* pNativeData) { - if (null == value) - { - return IntPtr.Zero; - } - - int length = Encoding.UTF8.GetByteCount(value); - byte* buffer = (byte*)Marshal.AllocHGlobal(length + 1).ToPointer(); - - if (length > 0) - { - fixed (char* pValue = value) - { - Encoding.UTF8.GetBytes(pValue, value.Length, buffer, length); - } - } - - buffer[length] = 0; - - return new IntPtr(buffer); + return FromNative(Encoding, (byte*)pNativeData); } - public static unsafe String FromNative(IntPtr pNativeData) + public static string FromNative(IntPtr pNativeData) { - if (IntPtr.Zero == pNativeData) - { - return null; - } - - byte* start = (byte*)pNativeData; - byte* walk = start; - - // Find the end of the string - while (*walk != 0) - { - walk++; - } - - if (walk == start) - { - return String.Empty; - } - - return new String((sbyte*)pNativeData.ToPointer(), 0, (int)(walk - start), Encoding.UTF8); + return FromNative(Encoding, pNativeData); } - public static unsafe String FromNative(IntPtr pNativeData, int length) + public static string FromNative(IntPtr pNativeData, int length) { - if (IntPtr.Zero == pNativeData) - { - return null; - } - - if (0 == length) - { - return String.Empty; - } + return FromNative(Encoding, pNativeData, length); + } - return new String((sbyte*)pNativeData.ToPointer(), 0, length, Encoding.UTF8); + public static string FromBuffer(byte[] buffer) + { + return FromBuffer(Encoding, buffer); } - public static String Utf8FromBuffer(byte[] buffer) + public static string FromBuffer(byte[] buffer, int length) { - if (null == buffer) - { - return null; - } - - int length = 0; - int stop = buffer.Length; - - while (length < stop && - 0 != buffer[length]) - { - length++; - } - - if (0 == length) - { - return String.Empty; - } - - return Encoding.UTF8.GetString(buffer, 0, 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 f03a5cfa9..50b006b74 100644 --- a/LibGit2Sharp/Credentials.cs +++ b/LibGit2Sharp/Credentials.cs @@ -1,18 +1,17 @@ -namespace LibGit2Sharp +using System; + +namespace LibGit2Sharp { /// /// Class that holds credentials for remote repository access. /// - public class Credentials + public abstract class Credentials { /// - /// 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). + /// Callback to acquire a credential object. /// - public string Password { get; set; } + /// The newly created credential object. + /// 0 for success, < 0 to indicate an error, > 0 to indicate no credential was acquired. + protected internal abstract int GitCredentialHandler(out IntPtr cred); } } diff --git a/LibGit2Sharp/CurrentOperation.cs b/LibGit2Sharp/CurrentOperation.cs index be0ffebc8..9050e8235 100644 --- a/LibGit2Sharp/CurrentOperation.cs +++ b/LibGit2Sharp/CurrentOperation.cs @@ -1,59 +1,69 @@ namespace LibGit2Sharp { /// - /// Determines the pending operation of a git repository - ie, whether - /// an operation (merge, cherry-pick, etc) is in progress. + /// Determines the pending operation of a git repository - ie, whether + /// an operation (merge, cherry-pick, etc) is in progress. /// public enum CurrentOperation { /// - /// No operation is in progress. + /// No operation is in progress. /// None = 0, /// - /// A merge is in progress. + /// A merge is in progress. /// Merge = 1, /// - /// A revert is in progress. + /// A revert is in progress. /// Revert = 2, /// - /// A cherry-pick is in progress. + /// A sequencer revert is in progress. /// - CherryPick = 3, + RevertSequence = 3, /// - /// A bisect is in progress. + /// A cherry-pick is in progress. /// - Bisect = 4, + CherryPick = 4, /// - /// A rebase is in progress. + /// A sequencer cherry-pick is in progress. /// - Rebase = 5, + CherryPickSequence = 5, /// - /// A rebase --interactive is in progress. + /// A bisect is in progress. /// - RebaseInteractive = 6, + Bisect = 6, /// - /// A rebase --merge is in progress. + /// A rebase is in progress. /// - RebaseMerge = 7, + Rebase = 7, /// - /// A mailbox application (am) is in progress. + /// A rebase --interactive is in progress. /// - ApplyMailbox = 8, + RebaseInteractive = 8, /// - /// A mailbox application (am) or rebase is in progress. + /// A rebase --merge is in progress. /// - ApplyMailboxOrRebase = 9, + RebaseMerge = 9, + + /// + /// A mailbox application (am) is in progress. + /// + ApplyMailbox = 10, + + /// + /// A mailbox application (am) or rebase is in progress. + /// + 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 new file mode 100644 index 000000000..b11b4f540 --- /dev/null +++ b/LibGit2Sharp/DefaultCredentials.cs @@ -0,0 +1,22 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A credential object that will provide the "default" credentials + /// (logged-in user information) via NTLM or SPNEGO authentication. + /// + public sealed class DefaultCredentials : 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) + { + 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 0d7b31406..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() { @@ -13,7 +12,7 @@ protected override string Shorten() } /// - /// Gets the remote branch which is connected to this local one, or null if there is none. + /// Gets the remote branch which is connected to this local one, or null if there is none. /// public override Branch TrackedBranch { diff --git a/LibGit2Sharp/Diff.cs b/LibGit2Sharp/Diff.cs index 279eb6b21..857eb8ed1 100644 --- a/LibGit2Sharp/Diff.cs +++ b/LibGit2Sharp/Diff.cs @@ -1,89 +1,87 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.IO; +using System.Linq; +using System.Text; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// 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. - /// - /// Copied and renamed files currently cannot be detected, as the feature is not supported by libgit2 yet. - /// These files will be shown as a pair of Deleted/Added files. + /// 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. + /// + /// Copied and renamed files currently cannot be detected, as the feature is not supported by libgit2 yet. + /// These files will be shown as a pair of Deleted/Added files. /// public class Diff { private readonly Repository repo; - private GitDiffOptions BuildOptions(DiffOptions diffOptions, IEnumerable paths = null) + private static GitDiffOptions BuildOptions(DiffModifiers diffOptions, FilePath[] filePaths = null, MatchedPathsAggregator matchedPathsAggregator = null, CompareOptions compareOptions = null) { var options = new GitDiffOptions(); options.Flags |= GitDiffOptionFlags.GIT_DIFF_INCLUDE_TYPECHANGE; - if (diffOptions.HasFlag(DiffOptions.IncludeUntracked)) + compareOptions = compareOptions ?? new CompareOptions(); + options.ContextLines = (ushort)compareOptions.ContextLines; + options.InterhunkLines = (ushort)compareOptions.InterhunkLines; + + if (diffOptions.HasFlag(DiffModifiers.IncludeUntracked)) { options.Flags |= GitDiffOptionFlags.GIT_DIFF_INCLUDE_UNTRACKED | GitDiffOptionFlags.GIT_DIFF_RECURSE_UNTRACKED_DIRS | - GitDiffOptionFlags.GIT_DIFF_INCLUDE_UNTRACKED_CONTENT; + GitDiffOptionFlags.GIT_DIFF_SHOW_UNTRACKED_CONTENT; } - if (paths == null) + if (diffOptions.HasFlag(DiffModifiers.IncludeIgnored)) { - return options; + options.Flags |= GitDiffOptionFlags.GIT_DIFF_INCLUDE_IGNORED | + GitDiffOptionFlags.GIT_DIFF_RECURSE_IGNORED_DIRS; } - options.PathSpec = GitStrArrayIn.BuildFrom(ToFilePaths(repo, paths)); - return options; - } - - private static FilePath[] ToFilePaths(Repository repo, IEnumerable paths) - { - var filePaths = new List(); - - foreach (string path in paths) + if (diffOptions.HasFlag(DiffModifiers.IncludeUnmodified) || compareOptions.IncludeUnmodified || + (compareOptions.Similarity != null && + (compareOptions.Similarity.RenameDetectionMode == RenameDetectionMode.CopiesHarder || + compareOptions.Similarity.RenameDetectionMode == RenameDetectionMode.Exact))) { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("At least one provided path is either null or empty.", "paths"); - } - - filePaths.Add(BuildRelativePathFrom(repo, path)); + options.Flags |= GitDiffOptionFlags.GIT_DIFF_INCLUDE_UNMODIFIED; } - if (filePaths.Count == 0) + if (compareOptions.Algorithm == DiffAlgorithm.Patience) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_PATIENCE; + } + else if (compareOptions.Algorithm == DiffAlgorithm.Minimal) { - throw new ArgumentException("No path has been provided.", "paths"); + options.Flags |= GitDiffOptionFlags.GIT_DIFF_MINIMAL; } - return filePaths.ToArray(); - } + if (diffOptions.HasFlag(DiffModifiers.DisablePathspecMatch)) + { + options.Flags |= GitDiffOptionFlags.GIT_DIFF_DISABLE_PATHSPEC_MATCH; + } - private static string BuildRelativePathFrom(Repository repo, string path) - { - //TODO: To be removed when libgit2 natively implements this - if (!Path.IsPathRooted(path)) + if (compareOptions.IndentHeuristic) { - return path; + options.Flags |= GitDiffOptionFlags.GIT_DIFF_INDENT_HEURISTIC; } - string normalizedPath = Path.GetFullPath(path); + if (matchedPathsAggregator != null) + { + options.NotifyCallback = matchedPathsAggregator.OnGitDiffNotify; + } - if (!normalizedPath.StartsWith(repo.Info.WorkingDirectory, StringComparison.Ordinal)) + if (filePaths != null) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "Unable to process file '{0}'. This absolute filepath escapes out of the working directory of the repository ('{1}').", - normalizedPath, repo.Info.WorkingDirectory)); + options.PathSpec = GitStrArrayManaged.BuildFrom(filePaths); } - return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); + return options; } /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Diff() { } @@ -93,158 +91,435 @@ internal Diff(Repository repo) this.repo = repo; } - /// - /// 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 TreeChanges Compare(Tree oldTree, Tree newTree, IEnumerable paths = null) - { - using(GitDiffOptions options = BuildOptions(DiffOptions.None, paths)) - using (DiffListSafeHandle diff = BuildDiffListFromTrees( - oldTree != null ? oldTree.Id : null, - newTree != null ? newTree.Id : null, - options)) - { - return new TreeChanges(diff); - } + private static readonly IDictionary> HandleRetrieverDispatcher = BuildHandleRetrieverDispatcher(); + + private static IDictionary> BuildHandleRetrieverDispatcher() + { + return new Dictionary> + { + { DiffTargets.Index, IndexToTree }, + { DiffTargets.WorkingDirectory, WorkdirToTree }, + { DiffTargets.Index | DiffTargets.WorkingDirectory, WorkdirAndIndexToTree }, + }; } - private DiffListSafeHandle BuildDiffListFromTrees(ObjectId oldTree, ObjectId newTree, GitDiffOptions options) + private static readonly IDictionary> ChangesBuilders = new Dictionary> { - return Proxy.git_diff_tree_to_tree(repo.Handle, oldTree, newTree, options); + { 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. + /// 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 . + /// 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) { - using (GitDiffOptions options = BuildOptions(DiffOptions.None)) + return Compare(oldBlob, newBlob, null); + } + + /// + /// Show changes between two s. + /// + /// The you want to compare from. + /// The 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) + { + using (GitDiffOptions options = BuildOptions(DiffModifiers.None, compareOptions: compareOptions)) { return new ContentChanges(repo, oldBlob, newBlob, options); } } - private readonly IDictionary> handleRetrieverDispatcher = BuildHandleRetrieverDispatcher(); + /// + /// 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); + } - private static IDictionary> BuildHandleRetrieverDispatcher() + /// + /// 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 new Dictionary> - { - { DiffTargets.Index, IndexToTree }, - { DiffTargets.WorkingDirectory, WorkdirToTree }, - { DiffTargets.Index | DiffTargets.WorkingDirectory, WorkdirAndIndexToTree }, - }; + return Compare(oldTree, newTree, paths, null, null); } /// - /// Show changes between a and a selectable target. + /// Show changes between two s. /// - /// The to compare from. - /// The target to compare to. - /// The list of paths (either files or directories) that should be compared. - /// A containing the changes between the and the selected target. - [Obsolete("This method will be removed in the next release. Please use Compare(Tree, DiffTargets, IEnumerable) instead.")] - public virtual TreeChanges Compare(Tree oldTree, DiffTarget diffTarget, IEnumerable paths = null) + /// 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. + /// + /// A containing the changes between the and the . + public virtual T Compare(Tree oldTree, Tree newTree, IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions) where T : class, IDiffResult { - DiffTargets targets; + return Compare(oldTree, newTree, paths, explicitPathsOptions, null); + } - switch (diffTarget) + /// + /// 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, CompareOptions compareOptions) where T : class, IDiffResult + { + return Compare(oldTree, newTree, paths, null, compareOptions); + } + + /// + /// 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; + var diffOptions = DiffModifiers.None; + + if (explicitPathsOptions != null) { - case DiffTarget.Index: - targets = DiffTargets.Index; - break; + diffOptions |= DiffModifiers.DisablePathspecMatch; - case DiffTarget.WorkingDirectory: - targets = DiffTargets.WorkingDirectory; - break; + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) + { + diffOptions |= DiffModifiers.IncludeUnmodified; + } + } - default: - targets = DiffTargets.Index | DiffTargets.WorkingDirectory; - break; + DiffHandle diff = BuildDiffList(oldTreeId, newTreeId, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try + { + 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); + } - return Compare(oldTree, targets, paths); + /// + /// 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. + /// + /// 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) where T : class, IDiffResult + { + return Compare(oldTree, diffTargets, paths, explicitPathsOptions, null); } /// - /// Show changes between a and the Index, the Working Directory, or both. + /// 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. - /// A containing the changes between the and the selected target. - public virtual TreeChanges Compare(Tree oldTree, DiffTargets diffTargets, IEnumerable paths = null) + /// 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); + var comparer = HandleRetrieverDispatcher[diffTargets](repo); + ObjectId oldTreeId = oldTree != null ? oldTree.Id : null; - DiffOptions diffOptions = diffTargets.HasFlag(DiffTargets.WorkingDirectory) ? - DiffOptions.IncludeUntracked : DiffOptions.None; + DiffModifiers diffOptions = diffTargets.HasFlag(DiffTargets.WorkingDirectory) + ? DiffModifiers.IncludeUntracked + : DiffModifiers.None; - using (GitDiffOptions options = BuildOptions(diffOptions, paths)) - using (DiffListSafeHandle dl = BuildDiffListFromTreeAndComparer( - oldTree != null ? oldTree.Id : null, - comparer, options)) + if (explicitPathsOptions != null) { - return new TreeChanges(dl); + diffOptions |= DiffModifiers.DisablePathspecMatch; + + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) + { + diffOptions |= DiffModifiers.IncludeUnmodified; + } + } + + DiffHandle diff = BuildDiffList(oldTreeId, null, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try + { + return BuildDiffResult(diff); + } + catch + { + diff.SafeDispose(); + throw; } } /// - /// Show changes between the working directory and the index. + /// 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. - /// A containing the changes between the working directory and the index. - public virtual TreeChanges Compare(IEnumerable paths = null) + /// 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. + /// + /// 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. + /// + /// 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) where T : class, IDiffResult + { + return Compare(includeUntracked ? DiffModifiers.IncludeUntracked : DiffModifiers.None, paths, explicitPathsOptions); + } + + /// + /// 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 + { + 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); - using (GitDiffOptions options = BuildOptions(DiffOptions.None, paths)) - using (DiffListSafeHandle dl = BuildDiffListFromComparer(null, comparer, options)) + if (explicitPathsOptions != null) + { + diffOptions |= DiffModifiers.DisablePathspecMatch; + + if (explicitPathsOptions.ShouldFailOnUnmatchedPath || explicitPathsOptions.OnUnmatchedPath != null) + { + diffOptions |= DiffModifiers.IncludeUnmodified; + } + } + + DiffHandle diff = BuildDiffList(null, null, comparer, diffOptions, paths, explicitPathsOptions, compareOptions); + + try { - return new TreeChanges(dl); + return BuildDiffResult(diff); + } + catch + { + diff.SafeDispose(); + throw; } } - private delegate DiffListSafeHandle TreeComparisonHandleRetriever(ObjectId id, GitDiffOptions options); + internal delegate DiffHandle TreeComparisonHandleRetriever(ObjectId oldTreeId, ObjectId newTreeId, GitDiffOptions options); + + private static TreeComparisonHandleRetriever TreeToTree(Repository repo) + { + return (oh, nh, o) => Proxy.git_diff_tree_to_tree(repo.Handle, oh, nh, o); + } private static TreeComparisonHandleRetriever WorkdirToIndex(Repository repo) { - return (h, o) => Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o); + return (oh, nh, o) => Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o); } private static TreeComparisonHandleRetriever WorkdirToTree(Repository repo) { - return (h, o) => Proxy.git_diff_tree_to_workdir(repo.Handle, h, o); + return (oh, nh, o) => Proxy.git_diff_tree_to_workdir(repo.Handle, oh, o); } private static TreeComparisonHandleRetriever WorkdirAndIndexToTree(Repository repo) { - TreeComparisonHandleRetriever comparisonHandleRetriever = (h, o) => + TreeComparisonHandleRetriever comparisonHandleRetriever = (oh, nh, o) => { - DiffListSafeHandle diff = null, diff2 = null; + DiffHandle diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); - try + using (DiffHandle diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o)) { - diff = Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, h, o); - diff2 = Proxy.git_diff_index_to_workdir(repo.Handle, repo.Index.Handle, o); Proxy.git_diff_merge(diff, diff2); } - catch - { - diff.SafeDispose(); - throw; - } - finally - { - diff2.SafeDispose(); - } return diff; }; @@ -254,17 +529,151 @@ private static TreeComparisonHandleRetriever WorkdirAndIndexToTree(Repository re private static TreeComparisonHandleRetriever IndexToTree(Repository repo) { - return (h, o) => Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, h, o); + return (oh, nh, o) => Proxy.git_diff_tree_to_index(repo.Handle, repo.Index.Handle, oh, o); + } + + private DiffHandle BuildDiffList( + ObjectId oldTreeId, + ObjectId newTreeId, + TreeComparisonHandleRetriever comparisonHandleRetriever, + DiffModifiers diffOptions, + IEnumerable paths, + ExplicitPathsOptions explicitPathsOptions, + CompareOptions compareOptions) + { + 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 (matchedPaths != null) + { + try + { + DispatchUnmatchedPaths(explicitPathsOptions, filePaths, matchedPaths); + } + catch + { + diffList.Dispose(); + throw; + } + } + + DetectRenames(diffList, compareOptions); + + return diffList; + } + } + + private static void DetectRenames(DiffHandle diffList, CompareOptions compareOptions) + { + var similarityOptions = (compareOptions == null) ? null : compareOptions.Similarity; + if (similarityOptions == null || similarityOptions.RenameDetectionMode == RenameDetectionMode.Default) + { + Proxy.git_diff_find_similar(diffList, null); + return; + } + + if (similarityOptions.RenameDetectionMode == RenameDetectionMode.None) + { + return; + } + + var opts = new GitDiffFindOptions + { + RenameThreshold = (ushort)similarityOptions.RenameThreshold, + RenameFromRewriteThreshold = (ushort)similarityOptions.RenameFromRewriteThreshold, + CopyThreshold = (ushort)similarityOptions.CopyThreshold, + BreakRewriteThreshold = (ushort)similarityOptions.BreakRewriteThreshold, + RenameLimit = (UIntPtr)similarityOptions.RenameLimit, + }; + + switch (similarityOptions.RenameDetectionMode) + { + case RenameDetectionMode.Exact: + opts.Flags = GitDiffFindFlags.GIT_DIFF_FIND_EXACT_MATCH_ONLY | + GitDiffFindFlags.GIT_DIFF_FIND_RENAMES | + GitDiffFindFlags.GIT_DIFF_FIND_COPIES | + GitDiffFindFlags.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + break; + case RenameDetectionMode.Renames: + opts.Flags = GitDiffFindFlags.GIT_DIFF_FIND_RENAMES; + break; + case RenameDetectionMode.Copies: + opts.Flags = GitDiffFindFlags.GIT_DIFF_FIND_RENAMES | + GitDiffFindFlags.GIT_DIFF_FIND_COPIES; + break; + case RenameDetectionMode.CopiesHarder: + opts.Flags = GitDiffFindFlags.GIT_DIFF_FIND_RENAMES | + GitDiffFindFlags.GIT_DIFF_FIND_COPIES | + GitDiffFindFlags.GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED; + break; + } + + if (!compareOptions.IncludeUnmodified) + { + opts.Flags |= GitDiffFindFlags.GIT_DIFF_FIND_REMOVE_UNMODIFIED; + } + + switch (similarityOptions.WhitespaceMode) + { + case WhitespaceMode.DontIgnoreWhitespace: + opts.Flags |= GitDiffFindFlags.GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE; + break; + case WhitespaceMode.IgnoreLeadingWhitespace: + opts.Flags |= GitDiffFindFlags.GIT_DIFF_FIND_IGNORE_LEADING_WHITESPACE; + break; + case WhitespaceMode.IgnoreAllWhitespace: + opts.Flags |= GitDiffFindFlags.GIT_DIFF_FIND_IGNORE_WHITESPACE; + break; + } + + Proxy.git_diff_find_similar(diffList, opts); } - private static DiffListSafeHandle BuildDiffListFromTreeAndComparer(ObjectId treeId, TreeComparisonHandleRetriever comparisonHandleRetriever, GitDiffOptions options) + private static void DispatchUnmatchedPaths( + ExplicitPathsOptions explicitPathsOptions, + IEnumerable filePaths, + IEnumerable matchedPaths) { - return BuildDiffListFromComparer(treeId, comparisonHandleRetriever, options); + List unmatchedPaths = (filePaths != null ? + filePaths.Except(matchedPaths) : Enumerable.Empty()).ToList(); + + if (unmatchedPaths.Count == 0) + { + return; + } + + if (explicitPathsOptions.OnUnmatchedPath != null) + { + unmatchedPaths.ForEach(filePath => explicitPathsOptions.OnUnmatchedPath(filePath.Native)); + } + + if (explicitPathsOptions.ShouldFailOnUnmatchedPath) + { + throw new UnmatchedPathException(BuildUnmatchedPathsMessage(unmatchedPaths)); + } } - private static DiffListSafeHandle BuildDiffListFromComparer(ObjectId treeId, TreeComparisonHandleRetriever comparisonHandleRetriever, GitDiffOptions options) + private static string BuildUnmatchedPathsMessage(List unmatchedPaths) { - return comparisonHandleRetriever(treeId, options); + var message = new StringBuilder("There were some unmatched paths:" + Environment.NewLine); + unmatchedPaths.ForEach(filePath => message.AppendFormat("- {0}{1}", filePath.Native, Environment.NewLine)); + + return message.ToString(); } } } 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/DiffModifiers.cs b/LibGit2Sharp/DiffModifiers.cs new file mode 100644 index 000000000..af9ccb75c --- /dev/null +++ b/LibGit2Sharp/DiffModifiers.cs @@ -0,0 +1,40 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Additional behaviors the diffing should take into account + /// when performing the comparison. + /// + [Flags] + internal enum DiffModifiers + { + /// + /// No special behavior. + /// + None = 0, + + /// + /// Include untracked files among the files to be processed, when + /// diffing against the working directory. + /// + IncludeUntracked = (1 << 1), + + /// + /// Include unmodified files among the files to be processed, when + /// diffing against the working directory. + /// + IncludeUnmodified = (1 << 2), + + /// + /// Treats the passed pathspecs as explicit paths (no pathspec match). + /// + DisablePathspecMatch = (1 << 3), + + /// + /// Include ignored files among the files to be processed, when + /// diffing against the working directory. + /// + IncludeIgnored = (1 << 4), + } +} diff --git a/LibGit2Sharp/DiffOptions.cs b/LibGit2Sharp/DiffOptions.cs deleted file mode 100644 index 133e5a40e..000000000 --- a/LibGit2Sharp/DiffOptions.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// Additional behaviors the diffing should take into account - /// when performing the comparison. - /// - [Flags] - internal enum DiffOptions - { - /// - /// No special behavior. - /// - None, - - /// - /// Include untracked files among the files to be processed, when - /// diffing against the working directory. - /// - IncludeUntracked, - } -} diff --git a/LibGit2Sharp/DiffTarget.cs b/LibGit2Sharp/DiffTarget.cs deleted file mode 100644 index e13911471..000000000 --- a/LibGit2Sharp/DiffTarget.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// The target of a Tree based diff comparison. - /// - [Obsolete("This type will be removed in the next release. Please use DiffTargets instead.")] - public enum DiffTarget - { - /// - /// The working directory. - /// - WorkingDirectory, - - /// - /// The repository index. - /// - Index, - - /// - /// Both the working directory and the repository index. - /// - BothWorkingDirectoryAndIndex, - } -} diff --git a/LibGit2Sharp/DiffTargets.cs b/LibGit2Sharp/DiffTargets.cs index 8488a9a03..40203ee60 100644 --- a/LibGit2Sharp/DiffTargets.cs +++ b/LibGit2Sharp/DiffTargets.cs @@ -3,19 +3,19 @@ namespace LibGit2Sharp { /// - /// The targets of a Tree based diff comparison. + /// The targets of a Tree based diff comparison. /// [Flags] public enum DiffTargets { /// - /// The repository index. + /// The repository index. /// Index = 1, /// - /// The working directory. + /// The working directory. /// WorkingDirectory = 2, } -} \ No newline at end of file +} diff --git a/LibGit2Sharp/DirectReference.cs b/LibGit2Sharp/DirectReference.cs index a79825c15..b9cc304b3 100644 --- a/LibGit2Sharp/DirectReference.cs +++ b/LibGit2Sharp/DirectReference.cs @@ -1,36 +1,45 @@ -using LibGit2Sharp.Core.Compat; +using System; namespace LibGit2Sharp { /// - /// A DirectReference points directly to a + /// A DirectReference points directly to a /// public class DirectReference : Reference { private readonly Lazy targetBuilder; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected DirectReference() { } - internal DirectReference(string canonicalName, Repository repo, ObjectId targetId) - : base(canonicalName, targetId.Sha) + 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 + /// Gets the target of this /// + /// Throws if Local Repository is not set. public virtual GitObject Target { get { return targetBuilder.Value; } } /// - /// As a is already peeled, invoking this will return the same . + /// As a is already peeled, invoking this will return the same . /// /// This instance. public override DirectReference ResolveToDirectReference() diff --git a/LibGit2Sharp/EmptyCommitException.cs b/LibGit2Sharp/EmptyCommitException.cs new file mode 100644 index 000000000..00d1081e5 --- /dev/null +++ b/LibGit2Sharp/EmptyCommitException.cs @@ -0,0 +1,60 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif + +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. + /// + /// 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. + /// + /// 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 EmptyCommitException(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 EmptyCommitException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/EntryExistsException.cs b/LibGit2Sharp/EntryExistsException.cs new file mode 100644 index 000000000..3ebfbdfba --- /dev/null +++ b/LibGit2Sharp/EntryExistsException.cs @@ -0,0 +1,65 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif + +using LibGit2Sharp.Core; + +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. + /// + /// 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. + /// + /// 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 EntryExistsException(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 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/ExplicitPathsOptions.cs b/LibGit2Sharp/ExplicitPathsOptions.cs new file mode 100644 index 000000000..a545f2c8b --- /dev/null +++ b/LibGit2Sharp/ExplicitPathsOptions.cs @@ -0,0 +1,37 @@ +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Allows callers to specify how unmatched paths should be handled + /// by operations such as Reset(), Compare(), Unstage(), ... + /// + /// By passing these options, the passed paths will be treated as + /// explicit paths, and NOT pathspecs containing globs. + /// + /// + public sealed class ExplicitPathsOptions + { + /// + /// Associated paths will be treated as explicit paths. + /// + public ExplicitPathsOptions() + { + ShouldFailOnUnmatchedPath = true; + } + + /// + /// When set to true, the called operation will throw a when an unmatched + /// path is encountered. + /// + /// Set to true by default. + /// + /// + public bool ShouldFailOnUnmatchedPath { get; set; } + + /// + /// Sets a callback that will be called once for each unmatched path. + /// + public UnmatchedPathHandler OnUnmatchedPath { get; set; } + } +} diff --git a/LibGit2Sharp/FetchHead.cs b/LibGit2Sharp/FetchHead.cs index df1b4006c..812865cf3 100644 --- a/LibGit2Sharp/FetchHead.cs +++ b/LibGit2Sharp/FetchHead.cs @@ -4,22 +4,31 @@ namespace LibGit2Sharp { /// - /// Represents a local reference data from a remote repository which - /// has been retreived through a Fetch process. + /// Represents a local reference data from a remote repository which + /// has been retreived through a Fetch process. /// - public class FetchHead : ReferenceWrapper + internal class FetchHead : ReferenceWrapper { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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; @@ -27,7 +36,7 @@ internal FetchHead(Repository repo, string remoteCanonicalName, } /// - /// Returns "FETCH_HEAD[i]", where i is the index of this fetch head. + /// Returns "FETCH_HEAD[i]", where i is the index of this fetch head. /// protected override string Shorten() { @@ -35,13 +44,13 @@ protected override string Shorten() } /// - /// Gets the canonical name of the reference this - /// points to in the remote repository it's been fetched from. + /// Gets the canonical name of the reference this + /// points to in the remote repository it's been fetched from. /// public virtual string RemoteCanonicalName { get; private set; } /// - /// Gets the that this fetch head points to. + /// Gets the that this fetch head points to. /// public virtual GitObject Target { @@ -49,13 +58,13 @@ public virtual GitObject Target } /// - /// The URL of the remote repository this - /// has been built from. + /// 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. + /// Determines if this fetch head entry has been explicitly fetched. /// public virtual bool ForMerge { get; private set; } } diff --git a/LibGit2Sharp/FetchOptions.cs b/LibGit2Sharp/FetchOptions.cs new file mode 100644 index 000000000..6f354a5d5 --- /dev/null +++ b/LibGit2Sharp/FetchOptions.cs @@ -0,0 +1,58 @@ +namespace LibGit2Sharp +{ + /// + /// Collection of parameters controlling Fetch behavior. + /// + public sealed class FetchOptions : FetchOptionsBase + { + /// + /// Specifies the tag-following behavior of the fetch operation. + /// + /// If not set, the fetch operation will follow the default behavior for the + /// based on the remote's configuration. + /// + /// If neither this property nor the remote `tagopt` configuration is set, + /// this will default to (i.e. tags that point to objects + /// retrieved during this fetch will be retrieved as well). + /// + public TagFetchMode? TagFetchMode { get; set; } + + /// + /// 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 bool? Prune { get; set; } + + /// + /// Specifies the depth of the fetch to perform. + /// + /// Default value is 0 (full fetch). + /// + /// + public int Depth { get; set; } = 0; + + /// + /// 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 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 c0d2df000..fbd32affd 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -3,69 +3,84 @@ namespace LibGit2Sharp { /// - /// Calculated status of a filepath in the working directory considering the current and the . + /// Calculated status of a filepath in the working directory considering the current and the . /// [Flags] public enum FileStatus { /// - /// The file doesn't exist. + /// The file doesn't exist. /// Nonexistent = (1 << 31), /// - /// The file hasn't been modified. + /// The file hasn't been modified. /// Unaltered = 0, /* GIT_STATUS_CURRENT */ /// - /// New file has been added to the Index. It's unknown from the Head. + /// 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. + /// 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. + /// 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. + /// The renaming of a file has been promoted from the working directory to the Index. A previous version exists in the Head. /// - Renamed = (1 << 3), /* GIT_STATUS_INDEX_RENAMED */ + RenamedInIndex = (1 << 3), /* GIT_STATUS_INDEX_RENAMED */ /// - /// A change in type for a file has been promoted from the working directory to the Index. A previous version exists in the Head. + /// 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. + /// 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. + /// 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. + /// 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. + /// 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 is but its name and/or path matches an exclude pattern in a gitignore file. + /// 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 */ + + /// + /// The file is unreadable in the working directory. + /// + Unreadable = (1 << 12), /* GIT_STATUS_WT_UNREADABLE */ + + /// + /// 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 index a2a441c49..0ab999f19 100644 --- a/LibGit2Sharp/Filter.cs +++ b/LibGit2Sharp/Filter.cs @@ -1,87 +1,403 @@ -using System.Collections; +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 { /// - /// Criterias used to filter out and order the commits of the repository when querying its history. + /// 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 class Filter + 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); + } + /// - /// Initializes a new instance of . + /// Determines whether the specified is equal to the current . /// - public Filter() + /// The to compare with the current . + /// True if the specified is equal to the current ; otherwise, false. + public bool Equals(Filter other) { - SortBy = GitSortOptions.Time; - Since = "HEAD"; + return equalityHelper.Equals(this, other); } /// - /// The ordering stragtegy to use. - /// - /// By default, the commits are shown in reverse chronological order. - /// + /// Returns the hash code for this instance. /// - public GitSortOptions SortBy { get; set; } + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return equalityHelper.GetHashCode(this); + } /// - /// A pointer to a commit object or a list of pointers to consider as starting points. - /// - /// Can be either a containing the sha or reference canonical name to use, - /// a , a , a , a , - /// a , an or even a mixed collection of all of the above. - /// By default, the will be used as boundary. - /// + /// Tests if two are equal. /// - public object Since { get; set; } + /// 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); + } - internal IList SinceList + /// + /// 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) { - get { return ToList(Since); } + return !Equals(left, right); } /// - /// A pointer to a commit object or a list of pointers which will be excluded (along with ancestors) from the enumeration. - /// - /// Can be either a containing the sha or reference canonical name to use, - /// a , a , a , a , - /// a , an or even a mixed collection of all of the above. - /// + /// 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). /// - public object Until { get; set; } + 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; + } - internal IList UntilList + int StreamCreateCallback(out IntPtr git_writestream_out, GitFilter self, IntPtr payload, IntPtr filterSourcePtr, IntPtr git_writestream_next) { - get { return ToList(Until); } + 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; } - private static IList ToList(object obj) + void StreamFreeCallback(IntPtr stream) { - var list = new List(); + StreamState state; - if (obj == null) + try { - return list; + 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)); + } - var types = new[] - { - typeof(string), typeof(ObjectId), - typeof(Commit), typeof(TagAnnotation), - typeof(Tag), typeof(Branch), typeof(DetachedHead), - typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) - }; + Ensure.ArgumentIsExpectedIntPtr(stream, state.thisPtr, "stream"); - if (types.Contains(obj.GetType())) + 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) { - list.Add(obj); - return list; + 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; } - list.AddRange(((IEnumerable)obj).Cast()); - return list; + 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 new file mode 100644 index 000000000..22988e62e --- /dev/null +++ b/LibGit2Sharp/FilteringOptions.cs @@ -0,0 +1,27 @@ +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Allows callers to specify how blob content filters will be applied. + /// + public sealed class FilteringOptions + { + /// + /// Initializes a new instance of the class. + /// + /// The path that a file would be checked out as + public FilteringOptions(string hintPath) + { + Ensure.ArgumentNotNull(hintPath, "hintPath"); + + this.HintPath = hintPath; + } + + /// + /// The path to "hint" to the filters will be used to apply + /// attributes. + /// + public string HintPath { get; private set; } + } +} diff --git a/LibGit2Sharp/GitLink.cs b/LibGit2Sharp/GitLink.cs new file mode 100644 index 000000000..f03b1d719 --- /dev/null +++ b/LibGit2Sharp/GitLink.cs @@ -0,0 +1,26 @@ +using System.Diagnostics; + +namespace LibGit2Sharp +{ + /// + /// Represents a gitlink (a reference to a commit in another Git repository) + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class GitLink : GitObject + { + /// + /// Needed for mocking purposes. + /// + protected GitLink() + { } + + internal GitLink(Repository repo, ObjectId id) + : base(repo, id) + { } + + private string DebuggerDisplay + { + get { return Id.ToString(); } + } + } +} diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 21edc0dc0..f9813a3ea 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -1,117 +1,155 @@ using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// A GitObject + /// A GitObject /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class GitObject : IEquatable + public abstract class GitObject : IEquatable, IBelongToARepository { - internal static GitObjectTypeMap TypeToTypeMap = - new GitObjectTypeMap - { - { typeof(Commit), GitObjectType.Commit }, - { typeof(Tree), GitObjectType.Tree }, - { typeof(Blob), GitObjectType.Blob }, - { typeof(TagAnnotation), GitObjectType.Tag }, - { typeof(GitObject), GitObjectType.Any }, - }; + internal static IDictionary TypeToKindMap = + new Dictionary + { + { 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. + /// The containing the object. /// - protected readonly Repository repo; + internal readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected GitObject() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The containing the object. - /// The it should be identified by. + /// The containing the object. + /// The it should be identified by. protected GitObject(Repository repo, ObjectId id) { this.repo = repo; Id = id; + lazyIsMissing = GitObjectLazyGroup.Singleton(repo, id, handle => handle == null, throwIfMissing: false); } /// - /// Gets the id of this object + /// Gets the id of this object /// public virtual ObjectId Id { get; private set; } /// - /// Gets the 40 character sha1 of this object. + /// Determine if the object is missing /// - public virtual string Sha - { - get { return Id.Sha; } - } + /// + /// 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 => 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)) { - if (peeledHandle == null) + throw new ArgumentException("Invalid type passed to peel"); + } + + using (var handle = Proxy.git_object_peel(repo.Handle, Id, kind, throwOnError)) + { + 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); } /// - /// 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 bool Equals(GitObject other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -120,10 +158,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(GitObject left, GitObject right) { @@ -131,19 +169,30 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(GitObject left, GitObject right) { return !Equals(left, right); } + /// + /// Returns the , a representation of the current . + /// + /// The that represents the current . + public override string ToString() + { + return Id.ToString(); + } + private string DebuggerDisplay { get { return Id.ToString(7); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } 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/GitObjectType.cs b/LibGit2Sharp/GitObjectType.cs deleted file mode 100644 index 36f658b70..000000000 --- a/LibGit2Sharp/GitObjectType.cs +++ /dev/null @@ -1,58 +0,0 @@ -namespace LibGit2Sharp -{ - /// - /// Underlying type of a - /// - public enum GitObjectType - { - /// - /// Object can be of any type. - /// - Any = -2, - - /// - /// Object is invalid. - /// - Bad = -1, - - /// - /// Reserved for future use. - /// - Ext1 = 0, - - /// - /// A commit object. - /// - Commit = 1, - - /// - /// A tree (directory listing) object. - /// - Tree = 2, - - /// - /// A file revision object. - /// - Blob = 3, - - /// - /// An annotated tag object. - /// - Tag = 4, - - /// - /// Reserved for future use. - /// - Ext2 = 5, - - /// - /// A delta, base is given by an offset. - /// - OfsDelta = 6, - - /// - /// A delta, base is given by object id. - /// - RefDelta = 7 - } -} diff --git a/LibGit2Sharp/GitSortOptions.cs b/LibGit2Sharp/GitSortOptions.cs deleted file mode 100644 index d56f97952..000000000 --- a/LibGit2Sharp/GitSortOptions.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; - -namespace LibGit2Sharp -{ - /// - /// Determines the sorting strategy when iterating through the content of the repository - /// - [Flags] - public enum GitSortOptions - { - /// - /// Sort the repository contents in no particular ordering; - /// this sorting is arbitrary, implementation-specific - /// and subject to change at any time. - /// - None = 0, - - /// - /// Sort the repository contents in topological order - /// (parents before children); this sorting mode - /// can be combined with time sorting. - /// - Topological = (1 << 0), - - /// - /// Sort the repository contents by commit time; - /// this sorting mode can be combined with - /// topological sorting. - /// - Time = (1 << 1), - - /// - /// Iterate through the repository contents in reverse - /// order; this sorting mode can be combined with - /// any of the above. - /// - Reverse = (1 << 2) - } -} diff --git a/LibGit2Sharp/GlobalSettings.cs b/LibGit2Sharp/GlobalSettings.cs new file mode 100644 index 000000000..9807155e7 --- /dev/null +++ b/LibGit2Sharp/GlobalSettings.cs @@ -0,0 +1,442 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Global settings for libgit2 and LibGit2Sharp. + /// + public static class GlobalSettings + { + private static readonly Lazy version = new Lazy(Version.Build); + private static readonly Dictionary registeredFilters; + private static readonly bool nativeLibraryPathAllowed; + + private static LogConfiguration logConfiguration = LogConfiguration.None; + + private static string nativeLibraryPath; + private static bool nativeLibraryPathLocked; + private static readonly string nativeLibraryDefaultPath = null; + + static GlobalSettings() + { + 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(); + } + +#if NETFRAMEWORK + private static string GetExecutingAssemblyDirectory() + { + // 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://")) + { + 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 + /// the scheme registered will delegate to the given transport + /// 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. + /// + /// The type of SmartSubtransport to register + /// The scheme (eg "http" or "gopher") to register + public static SmartSubtransportRegistration RegisterSmartSubtransport(string scheme) + where T : SmartSubtransport, new() + { + Ensure.ArgumentNotNull(scheme, "scheme"); + + var registration = new SmartSubtransportRegistration(scheme); + + try + { + Proxy.git_transport_register(registration.Scheme, + registration.FunctionPointer, + registration.RegistrationPointer); + } + catch (Exception) + { + registration.Free(); + throw; + } + + return registration; + } + + /// + /// Unregisters a previously registered + /// as a custom smart-protocol transport with libgit2. + /// + /// The type of SmartSubtransport to register + /// The previous registration + public static void UnregisterSmartSubtransport(SmartSubtransportRegistration registration) + where T : SmartSubtransport, new() + { + Ensure.ArgumentNotNull(registration, "registration"); + + 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 60eb11793..7e0b572c4 100644 --- a/LibGit2Sharp/Handlers.cs +++ b/LibGit2Sharp/Handlers.cs @@ -1,48 +1,175 @@ -namespace LibGit2Sharp.Handlers +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; + +namespace LibGit2Sharp.Handlers { /// - /// Delegate definition to handle Progress callback. - /// Returns the text as reported by the server. The text - /// in the serverProgressOutput parameter is not delivered - /// in any particular units (i.e. not necessarily delivered - /// as whole lines) and is likely to be chunked as partial lines. + /// Delegate definition to handle Progress callback. + /// Returns the text as reported by the server. The text + /// in the serverProgressOutput parameter is not delivered + /// in any particular units (i.e. not necessarily delivered + /// as whole lines) and is likely to be chunked as partial lines. /// /// text reported by the server. - /// Text can be chunked at arbitrary increments (i.e. can be composed - /// of a partial line of text). - public delegate void ProgressHandler(string serverProgressOutput); + /// Text can be chunked at arbitrary increments (i.e. can be composed + /// of a partial line of text). + /// True to continue, false to cancel. + public delegate bool ProgressHandler(string serverProgressOutput); /// - /// Delegate definition to handle UpdateTips callback. + /// Delegate definition to handle UpdateTips callback. /// /// Name of the updated reference. /// Old ID of the reference. /// New ID of the reference. - /// Return negative integer to cancel. - public delegate int UpdateTipsHandler(string referenceName, ObjectId oldId, ObjectId newId); + /// True to continue, false to cancel. + public delegate bool UpdateTipsHandler(string referenceName, ObjectId oldId, ObjectId newId); + + /// + /// Delegate definition for the credentials retrieval callback + /// + /// The url + /// Username which was extracted from the url, if any + /// 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. + /// + /// The object containing progress information. + /// 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 to handle Completion callback. + /// Delegate definition for callback reporting push network progress. /// - public delegate int CompletionHandler(RemoteCompletionType remoteCompletionType); + /// The current number of objects sent to server. + /// The total number of objects to send to the server. + /// The number of bytes sent to the server. + /// True to continue, false to cancel. + public delegate bool PushTransferProgressHandler(int current, int total, long bytes); /// - /// Delegate definition for transfer progress callback. + /// Delegate definition for callback reporting pack builder progress. /// - /// The object containing progress information. - public delegate void TransferProgressHandler(TransferProgress progress); + /// The current stage progress is being reported for. + /// The current number of objects processed in this this stage. + /// The total number of objects to process for the current stage. + /// True to continue, false to cancel. + public delegate bool PackBuilderProgressHandler(PackBuilderStage stage, int current, int total); /// - /// Delegate definition to handle reporting errors when updating references on the remote. + /// 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. /// /// The reference name and error from the server. public delegate void PushStatusErrorHandler(PushStatusError pushStatusErrors); /// - /// Delegate definition for checkout progress callback. + /// Delegate definition for checkout progress callback. /// /// Path of the updated file. /// Number of completed steps. /// Total number of steps. public delegate void CheckoutProgressHandler(string path, int completedSteps, int totalSteps); + + /// + /// Delegate definition for checkout notification callback. + /// + /// The path the callback corresponds to. + /// The checkout notification type. + /// True to continue checkout operation; false to cancel checkout operation. + public delegate bool CheckoutNotifyHandler(string path, CheckoutNotifyFlags notifyFlags); + + /// + /// Delegate definition for unmatched path callback. + /// + /// This callback will be called to notify the caller of unmatched path. + /// + /// + /// The unmatched path. + public delegate void UnmatchedPathHandler(string unmatchedPath); + + /// + /// Delegate definition for remote rename failure callback. + /// + /// This callback will be called to notify the caller of fetch refspecs + /// that haven't been automatically updated and need potential manual tweaking. + /// + /// + /// 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. + /// + public enum PackBuilderStage + { + /// + /// Counting stage. + /// + Counting, + + /// + /// Deltafying stage. + /// + 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 new file mode 100644 index 000000000..262c09c15 --- /dev/null +++ b/LibGit2Sharp/HistoryDivergence.cs @@ -0,0 +1,78 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Holds information about the potential ancestor + /// and distance from it and two specified s. + /// + public class HistoryDivergence + { + private readonly Lazy commonAncestor; + + /// + /// Needed for mocking purposes. + /// + protected HistoryDivergence() + { } + + internal HistoryDivergence(Repository repo, Commit one, Commit another) + { + commonAncestor = new Lazy(() => repo.ObjectDatabase.FindMergeBase(one, another)); + Tuple div = Proxy.git_graph_ahead_behind(repo.Handle, one, another); + + One = one; + Another = another; + AheadBy = div.Item1; + BehindBy = div.Item2; + } + + /// + /// Gets the being used as a reference. + /// + public virtual Commit One { get; private set; } + + /// + /// Gets the being compared against . + /// + public virtual Commit Another { get; private set; } + + /// + /// Gets the number of commits that are reachable from , + /// but not from . + /// + /// This property will return null when + /// and do not share a common ancestor. + /// + /// + public virtual int? AheadBy { get; private set; } + + /// + /// Gets the number of commits that are reachable from , + /// but not from . + /// + /// This property will return null when + /// and do not share a common ancestor. + /// + /// + public virtual int? BehindBy { get; private set; } + + /// + /// Returns the best possible common ancestor of + /// and or null if none found. + /// + public virtual Commit CommonAncestor + { + get { return commonAncestor.Value; } + } + } + + internal class NullHistoryDivergence : HistoryDivergence + { + public override Commit CommonAncestor + { + get { return null; } + } + } +} diff --git a/LibGit2Sharp/IBelongToARepository.cs b/LibGit2Sharp/IBelongToARepository.cs new file mode 100644 index 000000000..f0297c6cc --- /dev/null +++ b/LibGit2Sharp/IBelongToARepository.cs @@ -0,0 +1,26 @@ +namespace LibGit2Sharp +{ + /// + /// Can be used to reference the from which + /// an instance was created. + /// + /// While convenient in some situations (e.g. Checkout branch bound to UI element), + /// it is important to ensure instances created from an + /// are not used after it is disposed. + /// + /// + /// It's generally better to create and dependant instances + /// on demand, with a short lifespan. + /// + /// + public interface IBelongToARepository + { + /// + /// The from which this instance was created. + /// + /// The returned value should not be disposed. + /// + /// + IRepository Repository { get; } + } +} diff --git a/LibGit2Sharp/ICommitLog.cs b/LibGit2Sharp/ICommitLog.cs index 1f1b17bee..f773532fe 100644 --- a/LibGit2Sharp/ICommitLog.cs +++ b/LibGit2Sharp/ICommitLog.cs @@ -1,23 +1,15 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; namespace LibGit2Sharp { /// - /// A log of commits in a . + /// A log of commits in a . /// - public interface ICommitLog : ICommitCollection - { } - - /// - /// A collection of commits in a . - /// - [Obsolete("This interface will be removed in the next release. Please use ICommitLog instead.")] - public interface ICommitCollection : IEnumerable + public interface ICommitLog : IEnumerable { /// - /// Gets the current sorting strategy applied when enumerating the log. + /// Gets the current sorting strategy applied when enumerating the log. /// - GitSortOptions SortedBy { get; } + CommitSortStrategies SortedBy { get; } } } 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 dd5519bc3..ab8a92136 100644 --- a/LibGit2Sharp/IQueryableCommitLog.cs +++ b/LibGit2Sharp/IQueryableCommitLog.cs @@ -4,50 +4,31 @@ namespace LibGit2Sharp { /// - /// A log of commits in a that can be filtered with queries. + /// A log of commits in a that can be filtered with queries. /// - public interface IQueryableCommitLog : ICommitLog, IQueryableCommitCollection - { } - - /// - /// A collection of commits in a that can be filtered with queries. - /// - [Obsolete("This interface will be removed in the next release. Please use IQueryableCommitLog instead.")] - public interface IQueryableCommitCollection : ICommitCollection + public interface IQueryableCommitLog : ICommitLog { /// - /// Returns the list of commits of the repository matching the specified . + /// Returns the list of commits of the repository matching the specified . /// - /// The options used to control which commits will be returned. + /// The options used to control which commits will be returned. /// A list of commits, ready to be enumerated. - ICommitLog QueryBy(Filter filter); + ICommitLog QueryBy(CommitFilter filter); /// - /// 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. + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// 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. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - [Obsolete("This method will be removed in the next release. Please use Repository.Commit() instead.")] - Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit); + /// The file's path. + /// A list of file history entries, ready to be enumerated. + IEnumerable QueryBy(string path); /// - /// Find the best possible common ancestor given two s. + /// Returns the list of commits of the repository representing the history of a file beyond renames. /// - /// The first . - /// The second . - /// The common ancestor or null if none found. - Commit FindCommonAncestor(Commit first, Commit second); + /// 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); - /// - /// Find the best possible common ancestor given two or more s. - /// - /// The for which to find the common ancestor. - /// The common ancestor or null if none found. - Commit FindCommonAncestor(IEnumerable commits); } } diff --git a/LibGit2Sharp/IRepository.cs b/LibGit2Sharp/IRepository.cs index b6650fb97..fd19f9659 100644 --- a/LibGit2Sharp/IRepository.cs +++ b/LibGit2Sharp/IRepository.cs @@ -1,137 +1,156 @@ using System; using System.Collections.Generic; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// A Repository is the primary interface into a git repository + /// A Repository is the primary interface into a git repository /// public interface IRepository : IDisposable { /// - /// Shortcut to return the branch pointed to by HEAD + /// Shortcut to return the branch pointed to by HEAD /// - /// Branch Head { get; } /// - /// Provides access to the configuration settings for this repository. + /// Provides access to the configuration settings for this repository. /// Configuration Config { get; } /// - /// Gets the index. + /// Gets the index. /// Index Index { get; } /// - /// Lookup and enumerate references in the repository. + /// Lookup and enumerate references in the repository. /// ReferenceCollection Refs { get; } /// - /// Lookup and manage remotes in the repository. - /// - [Obsolete("This property will be removed in the next release. Please use Repository.Network.Remotes instead.")] - RemoteCollection Remotes { get; } - - /// - /// Lookup and enumerate commits in the repository. - /// Iterating this collection directly starts walking from the HEAD. + /// Lookup and enumerate commits in the repository. + /// Iterating this collection directly starts walking from the HEAD. /// IQueryableCommitLog Commits { get; } /// - /// Lookup and enumerate branches in the repository. + /// Lookup and enumerate branches in the repository. /// BranchCollection Branches { get; } /// - /// Lookup and enumerate tags in the repository. + /// Lookup and enumerate tags in the repository. /// TagCollection Tags { get; } /// - /// Provides high level information about this repository. + /// Provides high level information about this repository. /// RepositoryInformation Info { get; } /// - /// 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. + /// 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. + /// Gets the database. /// ObjectDatabase ObjectDatabase { get; } /// - /// Lookup notes in the repository. + /// Lookup notes in the repository. /// NoteCollection Notes { get; } /// - /// Checkout the specified . + /// Submodules in the repository. + /// + SubmoduleCollection Submodules { get; } + + /// + /// Worktrees in the repository. + /// + WorktreeCollection Worktrees { get; } + + /// + /// Checkout the specified tree. + /// + /// 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. + /// + /// This method does not switch branches or update the current repository HEAD. + /// + /// + /// A revparse spec for the commit or branch to checkout paths from. + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + void CheckoutPaths(string committishOrBranchSpec, IEnumerable paths, CheckoutOptions checkoutOptions); + + /// + /// Try to lookup an object by its . If no matching object is found, null will be returned. /// - /// The to check out. - /// controlling checkout behavior. - /// that checkout progress is reported through. - /// The that was checked out. - Branch Checkout(Branch branch, CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress); + /// The id to lookup. + /// The or null if it was not found. + GitObject Lookup(ObjectId id); /// - /// Checkout the specified branch, reference or SHA. + /// Try to lookup an object by its sha or a reference canonical name. If no matching object is found, null will be returned. /// - /// A revparse spec for the commit or branch to checkout. - /// Options controlling checkout behavior. - /// Callback method to report checkout progress updates through. - /// The new HEAD. - Branch Checkout(string committishOrBranchSpec, CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress); + /// A revparse spec for the object to lookup. + /// The or null if it was not found. + GitObject Lookup(string objectish); /// - /// Try to lookup an object by its and . If no matching object is found, null will be returned. + /// Try to lookup an object by its and . If no matching object is found, null will be returned. /// - /// The id to lookup. - /// The kind of GitObject being looked up - /// The or null if it was not found. - GitObject Lookup(ObjectId id, GitObjectType type = GitObjectType.Any); + /// The id to lookup. + /// The kind of GitObject being looked up + /// The or null if it was not found. + GitObject Lookup(ObjectId id, ObjectType type); /// - /// Try to lookup an object by its sha or a reference canonical name and . If no matching object is found, null will be returned. + /// Try to lookup an object by its sha or a reference canonical name and . If no matching object is found, null will be returned. /// - /// A revparse spec for the object to lookup. - /// The kind of being looked up - /// The or null if it was not found. - GitObject Lookup(string objectish, GitObjectType type = GitObjectType.Any); + /// A revparse spec for the object to lookup. + /// The kind of being looked up + /// The or null if it was not found. + GitObject Lookup(string objectish, ObjectType type); /// - /// 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. + /// 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 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. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - Commit Commit(string message, Signature author, Signature committer, bool amendPreviousCommit = false); + /// 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 that specify the commit behavior. + /// The generated . + Commit Commit(string message, Signature author, Signature committer, CommitOptions options); /// - /// Sets the current to the specified commit and optionally resets the and - /// the content of the working tree to match. + /// Sets the current 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. - void Reset(ResetOptions resetOptions, Commit commit); + /// Flavor of reset operation to perform. + /// The target commit object. + 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. /// - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - void Reset(Commit commit, IEnumerable paths = null); + /// Flavor of reset operation to perform. + /// The target commit object. + /// 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. @@ -139,13 +158,129 @@ public interface IRepository : IDisposable void RemoveUntrackedFiles(); /// - /// Gets the references to the tips that are currently being merged. + /// Revert the specified commit. + /// + /// The to revert. + /// The of who is performing the reverte. + /// controlling revert behavior. + /// The result of the revert. + RevertResult Revert(Commit commit, Signature reverter, RevertOptions options); + + /// + /// Merge changes from commit into the branch pointed at by HEAD.. + /// + /// The commit to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + MergeResult Merge(Commit commit, Signature merger, MergeOptions options); + + /// + /// Merges changes from branch into the branch pointed at by HEAD.. + /// + /// The branch to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + MergeResult Merge(Branch branch, Signature merger, MergeOptions options); + + /// + /// Merges changes from the commit into the branch pointed at by HEAD. + /// + /// The commit to merge into branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// 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. /// - IEnumerable MergeHeads { get; } + /// The commit to cherry pick into branch pointed at by HEAD. + /// The of who is performing the cherry pick. + /// Specifies optional parameters controlling cherry pick behavior; if null, the defaults are used. + /// The of the merge. + CherryPickResult CherryPick(Commit commit, Signature committer, CherryPickOptions options); /// - /// Provides access to network functionality for a repository. + /// Manipulate the currently ignored files. + /// + Ignore Ignore { get; } + + /// + /// Provides access to network functionality for a repository. /// Network Network { get; } + + /// + /// Lookup and enumerate stashes in the repository. + /// + StashCollection Stashes { get; } + + /// + /// 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. + 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/Ignore.cs b/LibGit2Sharp/Ignore.cs index d31789389..63ec6b264 100644 --- a/LibGit2Sharp/Ignore.cs +++ b/LibGit2Sharp/Ignore.cs @@ -6,15 +6,15 @@ namespace LibGit2Sharp { /// - /// Provides methods to manage the rules ensuring that some specific - /// untracked files are ignored. + /// Provides methods to manage the rules ensuring that some specific + /// untracked files are ignored. /// public class Ignore { private readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Ignore() { } @@ -25,8 +25,8 @@ internal Ignore(Repository repo) } /// - /// Adds a custom .gitignore rule that will be applied to futher operations to the Index. This is in addition - /// to the standard .gitignore rules that would apply as a result of the system/user/repo .gitignore + /// Adds a custom .gitignore rule that will be applied to futher operations to the Index. This is in addition + /// to the standard .gitignore rules that would apply as a result of the system/user/repo .gitignore /// /// The content of a .gitignore file that will be applied. public virtual void AddTemporaryRules(IEnumerable rules) @@ -44,9 +44,9 @@ public virtual void AddTemporaryRules(IEnumerable rules) } /// - /// Resets all custom rules that were applied via calls to - /// - note that this will not affect - /// the application of the user/repo .gitignores. + /// Resets all custom rules that were applied via calls to + /// - note that this will not affect + /// the application of the user/repo .gitignores. /// public virtual void ResetAllTemporaryRules() { @@ -54,8 +54,8 @@ public virtual void ResetAllTemporaryRules() } /// - /// Given a relative path, this method determines whether a path should be ignored, checking - /// both the custom ignore rules as well as the "normal" .gitignores. + /// Given a relative path, this method determines whether a path should be ignored, checking + /// both the custom ignore rules as well as the "normal" .gitignores. /// /// A path relative to the repository /// true if the path should be ignored. diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 5ae6adce5..321673606 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -3,35 +3,39 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.IO; using System.Runtime.InteropServices; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// The Index is a staging area between the Working directory and the Repository. - /// It's used to prepare and aggregate the changes that will be part of the next commit. + /// The Index is a staging area between the Working directory and the Repository. + /// It's used to prepare and aggregate the changes that will be part of the next commit. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class Index : IEnumerable { - private readonly IndexSafeHandle handle; + private readonly IndexHandle handle; private readonly Repository repo; + private readonly ConflictCollection conflicts; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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); + internal Index(Repository repo) + : this(Proxy.git_repository_index(repo.Handle), repo) + { repo.RegisterForCleanup(handle); } @@ -41,17 +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(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 { @@ -59,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 { @@ -67,45 +72,30 @@ public virtual bool IsFullyMerged } /// - /// Gets the with the specified relative path. + /// 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(repo, 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); - return IndexEntry.BuildFromPtr(repo, entryHandle); + git_index_entry* entryHandle = Proxy.git_index_get_byindex(handle, (UIntPtr)index); + return IndexEntry.BuildFromPtr(entryHandle); } } #region IEnumerable Members - private class OrdinalComparer : IComparer - { - Func accessor; - - public OrdinalComparer(Func accessor) - { - this.accessor = accessor; - } - - public int Compare(T x, T y) - { - return string.CompareOrdinal(accessor(x), accessor(y)); - } - } - private List AllIndexEntries() { var entryCount = Count; @@ -116,23 +106,22 @@ private List AllIndexEntries() list.Add(this[i]); } - list.Sort(new OrdinalComparer(i => i.Path)); return list; } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return AllIndexEntries().GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -141,388 +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). - /// - /// The path of the file within the working directory. - public virtual void Stage(string path) - { - Ensure.ArgumentNotNull(path, "path"); - - Stage(new[] { path }); - } - - /// - /// Promotes to the staging area the latest modifications of a collection of files in the working directory (addition, updation or removal). + /// 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. - public virtual void Stage(IEnumerable paths) + /// The to read the entries from. + public virtual void Replace(Tree source) { - //TODO: Stage() should support following use cases: - // - Recursively staging the content of a directory - - IEnumerable> batch = PrepareBatch(paths); - - foreach (KeyValuePair kvp in batch) + using (var obj = new ObjectSafeWrapper(source.Id, repo.Handle)) { - if (Directory.Exists(kvp.Key)) - { - throw new NotImplementedException(); - } - - if (!kvp.Value.HasFlag(FileStatus.Nonexistent)) - { - continue; - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Can not stage '{0}'. The file does not exist.", kvp.Key)); + Proxy.git_index_read_fromtree(this, obj.ObjectPtr); } - - foreach (KeyValuePair kvp in batch) - { - string relativePath = kvp.Key; - FileStatus fileStatus = kvp.Value; - - if (fileStatus.HasFlag(FileStatus.Missing)) - { - RemoveFromIndex(relativePath); - } - else - { - AddToIndex(relativePath); - } - } - - UpdatePhysicalIndex(); } /// - /// Removes from the staging area all the modifications of a 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 path of the file within the working directory. - public virtual void Unstage(string path) + public virtual void Clear() { - Ensure.ArgumentNotNull(path, "path"); - - Unstage(new[] { path }); + Proxy.git_index_clear(this); } - /// - /// Removes from the staging area all the modifications of a collection of file since the latest commit (addition, updation or removal). - /// - /// The collection of paths of the files within the working directory. - public virtual void Unstage(IEnumerable paths) + private void RemoveFromIndex(string relativePath) { - Ensure.ArgumentNotNull(paths, "paths"); - - if (repo.Info.IsHeadOrphaned) - { - TreeChanges changes = repo.Diff.Compare(null, DiffTargets.Index, paths); - - Reset(changes); - } - else - { - repo.Reset("HEAD", paths); - } + Proxy.git_index_remove_bypath(handle, relativePath); } /// - /// Moves and/or renames a file in the working directory and promotes the change to the staging area. + /// Removes a specified entry from the . /// - /// 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) + /// The path of the entry to be removed. + public virtual void Remove(string indexEntryPath) { - Move(new[] { sourcePath }, new[] { destinationPath }); + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); + RemoveFromIndex(indexEntryPath); } /// - /// Moves and/or renames a collection of files in the working directory and promotes the changes to the staging area. + /// Adds a file from the working directory in the . + /// + /// If an entry with the same path already exists in the , + /// the newly added one will overwrite it. + /// /// - /// 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, in the working directory, of the file to be added. + public virtual void Add(string pathInTheWorkdir) { - 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[] { 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[] { 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(pathInTheWorkdir, "pathInTheWorkdir"); + Proxy.git_index_add_bypath(handle, pathInTheWorkdir); } /// - /// Removes a file from the working directory and promotes the removal to the staging area. - /// - /// If the file has already been deleted from the working directory, this method will only deal - /// with promoting the removal to the staging area. - /// + /// Adds an entry in the from a . + /// + /// 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. - public virtual void Remove(string path) + /// 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.ArgumentNotNull(path, "path"); - - Remove(new[] { path }); + Ensure.ArgumentConformsTo(indexEntryMode, m => m.HasAny(TreeEntryDefinition.BlobModes), "indexEntryMode"); + Ensure.ArgumentNotNull(blob, "blob"); + Ensure.ArgumentNotNull(indexEntryPath, "indexEntryPath"); + AddEntryToTheIndex(indexEntryPath, blob.Id, indexEntryMode); } - /// - /// Removes a collection of files from the working directory and promotes the removal to the staging area. - /// - /// 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 collection of paths of the files within the working directory. - public virtual void Remove(IEnumerable paths) + internal void Replace(TreeChanges changes) { - //TODO: Remove() should support following use cases: - // - Removing a directory and its content - - IEnumerable> batch = PrepareBatch(paths); - - foreach (KeyValuePair keyValuePair in batch) + foreach (TreeEntryChanges treeEntryChanges in changes) { - if (Directory.Exists(keyValuePair.Key)) - { - throw new NotImplementedException(); - } - - if (!keyValuePair.Value.HasAny(new[] { FileStatus.Nonexistent, FileStatus.Removed, FileStatus.Modified, FileStatus.Untracked })) + switch (treeEntryChanges.Status) { - continue; - } + case ChangeKind.Unmodified: + continue; - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Unable to remove file '{0}'. Its current status is '{1}'.", keyValuePair.Key, keyValuePair.Value)); - } + case ChangeKind.Added: + RemoveFromIndex(treeEntryChanges.Path); + continue; - string wd = repo.Info.WorkingDirectory; - foreach (KeyValuePair keyValuePair in batch) - { - RemoveFromIndex(keyValuePair.Key); + case ChangeKind.Deleted: + case ChangeKind.Modified: + AddEntryToTheIndex(treeEntryChanges.OldPath, + treeEntryChanges.OldOid, + treeEntryChanges.OldMode); + continue; - if (File.Exists(Path.Combine(wd, keyValuePair.Key))) - { - File.Delete(Path.Combine(wd, keyValuePair.Key)); + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Entry '{0}' bears an unexpected ChangeKind '{1}'", + treeEntryChanges.Path, + treeEntryChanges.Status)); } } - - UpdatePhysicalIndex(); } - private IEnumerable> PrepareBatch(IEnumerable paths) + /// + /// Gets the conflicts that exist. + /// + public virtual ConflictCollection Conflicts { - Ensure.ArgumentNotNull(paths, "paths"); - - IDictionary dic = new Dictionary(); - - foreach (string path in paths) - { - if (string.IsNullOrEmpty(path)) - { - throw new ArgumentException("At least one provided path is either null or empty.", "paths"); - } - - string relativePath = BuildRelativePathFrom(repo, path); - FileStatus fileStatus = RetrieveStatus(relativePath); - - dic.Add(relativePath, fileStatus); - } - - if (dic.Count == 0) - { - throw new ArgumentException("No path has been provided.", "paths"); - } - - return dic; + get { return conflicts; } } - private IDictionary, Tuple> PrepareBatch(IEnumerable leftPaths, IEnumerable rightPaths) + private unsafe void AddEntryToTheIndex(string path, ObjectId id, Mode mode) { - IDictionary, Tuple> dic = new Dictionary, Tuple>(); - - IEnumerator leftEnum = leftPaths.GetEnumerator(); - IEnumerator rightEnum = rightPaths.GetEnumerator(); - - while (Enumerate(leftEnum, rightEnum)) + IntPtr pathPtr = StrictFilePathMarshaler.FromManaged(path); + var indexEntry = new git_index_entry { - Tuple from = BuildFrom(leftEnum.Current); - Tuple to = BuildFrom(rightEnum.Current); - dic.Add(from, to); - } - - return dic; - } + mode = (uint)mode, + path = (char*)pathPtr, + }; + Marshal.Copy(id.RawId, 0, new IntPtr(indexEntry.id.Id), GitOid.Size); - private Tuple BuildFrom(string path) - { - string relativePath = BuildRelativePathFrom(repo, path); - return new Tuple(relativePath, RetrieveStatus(relativePath)); + Proxy.git_index_add(handle, &indexEntry); + EncodingMarshaler.Cleanup(pathPtr); } - private static bool Enumerate(IEnumerator leftEnum, IEnumerator rightEnum) + private string DebuggerDisplay { - bool isLeftEoF = leftEnum.MoveNext(); - bool isRightEoF = rightEnum.MoveNext(); - - if (isLeftEoF == isRightEoF) + get { - return isLeftEoF; + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", Count); } - - throw new ArgumentException("The collection of paths are of different lengths."); - } - - private void AddToIndex(string relativePath) - { - Proxy.git_index_add_bypath(handle, relativePath); - } - - private void RemoveFromIndex(string relativePath) - { - Proxy.git_index_remove(handle, relativePath, 0); - } - - private void UpdatePhysicalIndex() - { - Proxy.git_index_write(handle); } - private static string BuildRelativePathFrom(Repository repo, string path) + /// + /// Replaces entries in the with entries from the specified . + /// + /// The target object. + public virtual void Replace(Commit commit) { - //TODO: To be removed when libgit2 natively implements this - if (!Path.IsPathRooted(path)) - { - return path; - } - - string normalizedPath = Path.GetFullPath(path); - - if (!normalizedPath.StartsWith(repo.Info.WorkingDirectory, StringComparison.Ordinal)) - { - 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)); - } - - return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); + 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 = BuildRelativePathFrom(repo, 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 . /// - /// A holding the state of all the files. - public virtual RepositoryStatus RetrieveStatus() + /// 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) { - return new RepositoryStatus(repo); - } + 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.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(); } - private void ReplaceIndexEntryWith(TreeEntryChanges treeEntryChanges) + /// + /// Write the contents of this to disk + /// + public virtual void Write() { - var indexEntry = new GitIndexEntry - { - Mode = (uint)treeEntryChanges.OldMode, - oid = treeEntryChanges.OldOid.Oid, - Path = FilePathMarshaler.FromManaged(treeEntryChanges.OldPath), - }; - - Proxy.git_index_add(handle, indexEntry); - Marshal.FreeHGlobal(indexEntry.Path); + 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 01991dcd2..554d9a9f1 100644 --- a/LibGit2Sharp/IndexEntry.cs +++ b/LibGit2Sharp/IndexEntry.cs @@ -7,89 +7,80 @@ namespace LibGit2Sharp { /// - /// A reference to a known by the . + /// A reference to a known by the . /// - [DebuggerDisplayAttribute("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class IndexEntry : IEquatable { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.Path, x => x.Id, x => x.Mode, x => x.StageLevel); - private Func state; - - /// - /// State of the version of the pointed at by this , - /// compared against the known from the and the file in the working directory. - /// - [Obsolete("This method will be removed in the next release. Please use Repository.Index.RetrieveStatus(filePath) overload instead.")] - public virtual FileStatus State - { - get { return state(); } - } - /// - /// Gets the relative path to the file within the working directory. + /// Gets the relative path to the file within the working directory. /// public virtual string Path { get; private set; } /// - /// Gets the file mode. + /// Gets the file mode. /// public virtual Mode Mode { get; private set; } /// - /// Gets the stage number. + /// Gets the stage number. /// public virtual StageLevel StageLevel { get; private set; } /// - /// Gets the id of the pointed at by this index entry. + /// 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(Repository repo, 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(); - - var path = FilePathMarshaler.FromNative(entry.Path); + string path = LaxUtf8Marshaler.FromNative(entry->path); return new IndexEntry - { - Path = path.Native, - Id = entry.oid, - state = () => repo.Index.RetrieveStatus(path.Native), - 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); } /// - /// 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 bool Equals(IndexEntry other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -98,10 +89,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(IndexEntry left, IndexEntry right) { @@ -109,10 +100,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(IndexEntry left, IndexEntry right) { @@ -124,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 new file mode 100644 index 000000000..40c202acc --- /dev/null +++ b/LibGit2Sharp/IndexNameEntry.cs @@ -0,0 +1,128 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// A reference to the paths involved in a rename , + /// known by the . + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class IndexNameEntry : IEquatable + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.Ancestor, x => x.Ours, x => x.Theirs); + + /// + /// Needed for mocking purposes. + /// + protected IndexNameEntry() + { } + + internal static unsafe IndexNameEntry BuildFromPtr(git_index_name_entry* entry) + { + if (entry == null) + { + return 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 + { + Ancestor = ancestor, + Ours = ours, + Theirs = theirs, + }; + } + + /// + /// Gets the path of the ancestor side of the conflict. + /// + public virtual string Ancestor { get; private set; } + + /// + /// Gets the path of the "ours" side of the conflict. + /// + public virtual string Ours { get; private set; } + + /// + /// Gets the path of the "theirs" side of the conflict. + /// + public virtual string Theirs { get; private set; } + + /// + /// 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 IndexNameEntry); + } + + /// + /// 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(IndexNameEntry 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 ==(IndexNameEntry left, IndexNameEntry 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 !=(IndexNameEntry left, IndexNameEntry right) + { + return !Equals(left, right); + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "{0} {1} {2}", + Ancestor, + Ours, + Theirs); + } + } + } +} diff --git a/LibGit2Sharp/IndexNameEntryCollection.cs b/LibGit2Sharp/IndexNameEntryCollection.cs new file mode 100644 index 000000000..a75bedd71 --- /dev/null +++ b/LibGit2Sharp/IndexNameEntryCollection.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The collection of s in a + /// index that reflect the + /// original paths of any rename conflicts that exist in the index. + /// + public class IndexNameEntryCollection : IEnumerable + { + private readonly Index index; + + /// + /// Needed for mocking purposes. + /// + protected IndexNameEntryCollection() + { } + + internal IndexNameEntryCollection(Index index) + { + this.index = index; + } + + private unsafe IndexNameEntry this[int idx] + { + get + { + git_index_name_entry* entryHandle = Proxy.git_index_name_get_byindex(index.Handle, (UIntPtr)idx); + return IndexNameEntry.BuildFromPtr(entryHandle); + } + } + + #region IEnumerable Members + + private List AllIndexNames() + { + var list = new List(); + + int count = Proxy.git_index_name_entrycount(index.Handle); + + for (int i = 0; i < count; i++) + { + list.Add(this[i]); + } + + return list; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return AllIndexNames().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(); + } + + #endregion + } +} diff --git a/LibGit2Sharp/IndexReucEntry.cs b/LibGit2Sharp/IndexReucEntry.cs new file mode 100644 index 000000000..becd20122 --- /dev/null +++ b/LibGit2Sharp/IndexReucEntry.cs @@ -0,0 +1,154 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// A reference to a resolved , + /// known by the . + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + 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); + + /// + /// Needed for mocking purposes. + /// + protected IndexReucEntry() + { } + + internal static unsafe IndexReucEntry BuildFromPtr(git_index_reuc_entry* entry) + { + if (entry == null) + { + return null; + } + + FilePath path = LaxUtf8Marshaler.FromNative(entry->Path); + + return new IndexReucEntry + { + Path = path.Native, + 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, + }; + } + + /// + /// Gets the path of this conflict. + /// + public virtual string Path { get; private set; } + + /// + /// Gets the that was the ancestor of this + /// conflict. + /// + public virtual ObjectId AncestorId { get; private set; } + + /// + /// Gets the of the file that was the ancestor of + /// conflict. + /// + public virtual Mode AncestorMode { get; private set; } + + /// + /// Gets the that was "our" side of this + /// conflict. + /// + public virtual ObjectId OurId { get; private set; } + + /// + /// Gets the of the file that was "our" side of + /// the conflict. + /// + public virtual Mode OurMode { get; private set; } + + /// + /// Gets the that was "their" side of this + /// conflict. + /// + public virtual ObjectId TheirId { get; private set; } + + /// + /// Gets the of the file that was "their" side of + /// the conflict. + /// + public virtual Mode TheirMode { get; private set; } + + /// + /// 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 IndexReucEntry); + } + + /// + /// 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(IndexReucEntry 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 ==(IndexReucEntry left, IndexReucEntry 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 !=(IndexReucEntry left, IndexReucEntry right) + { + return !Equals(left, right); + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "{0}: {1} {2} {3}", + Path, + AncestorId, + OurId, + TheirId); + } + } + } +} diff --git a/LibGit2Sharp/IndexReucEntryCollection.cs b/LibGit2Sharp/IndexReucEntryCollection.cs new file mode 100644 index 000000000..818bce70c --- /dev/null +++ b/LibGit2Sharp/IndexReucEntryCollection.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The collection of s in a + /// index that reflect the + /// resolved conflicts. + /// + public class IndexReucEntryCollection : IEnumerable + { + private readonly Index index; + + /// + /// Needed for mocking purposes. + /// + protected IndexReucEntryCollection() + { } + + internal IndexReucEntryCollection(Index index) + { + this.index = index; + } + + /// + /// Gets the with the specified relative path. + /// + public virtual unsafe IndexReucEntry this[string path] + { + get + { + Ensure.ArgumentNotNullOrEmptyString(path, "path"); + + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_bypath(index.Handle, path); + return IndexReucEntry.BuildFromPtr(entryHandle); + } + } + + private unsafe IndexReucEntry this[int idx] + { + get + { + git_index_reuc_entry* entryHandle = Proxy.git_index_reuc_get_byindex(index.Handle, (UIntPtr)idx); + return IndexReucEntry.BuildFromPtr(entryHandle); + } + } + + #region IEnumerable Members + + private List AllIndexReucs() + { + var list = new List(); + + int count = Proxy.git_index_reuc_entrycount(index.Handle); + + for (int i = 0; i < count; i++) + { + list.Add(this[i]); + } + + return list; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return AllIndexReucs().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(); + } + + #endregion + } +} diff --git a/LibGit2Sharp/InvalidSpecificationException.cs b/LibGit2Sharp/InvalidSpecificationException.cs index 3d497b737..d9625dc32 100644 --- a/LibGit2Sharp/InvalidSpecificationException.cs +++ b/LibGit2Sharp/InvalidSpecificationException.cs @@ -1,54 +1,75 @@ 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. + /// Initializes a new instance of the class. /// public InvalidSpecificationException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 InvalidSpecificationException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 5f65b6127..1c4abef7b 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -1,252 +1,61 @@ - - + + - Debug - AnyCPU - 8.0.30703 - 2.0 - {EE6ED99F-CB12-4683-B055-D28FC7357A34} - Library - Properties - LibGit2Sharp - LibGit2Sharp - v3.5 - 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;NET35 - 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;NET35;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/LibGit2SharpException.cs b/LibGit2Sharp/LibGit2SharpException.cs index c52553611..0518fa757 100644 --- a/LibGit2Sharp/LibGit2SharpException.cs +++ b/LibGit2Sharp/LibGit2SharpException.cs @@ -1,100 +1,61 @@ 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. - /// - [Obsolete("This type will be removed in the next release. Please use LibGit2SharpException instead.")] - [Serializable] - public class LibGit2Exception : LibGit2SharpException - { - /// - /// Initializes a new instance of the class. - /// - public LibGit2Exception() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// A message that describes the error. - public LibGit2Exception(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 LibGit2Exception(string message, Exception innerException) - : base(message, innerException) - { - } - } - - /// - /// The exception that is thrown when an error occurs during application execution. + /// 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. + /// Initializes a new instance of the class. /// public LibGit2SharpException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 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. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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(FormatMessage(message, code, category)) - { - Data.Add("libgit2.code", (int)code); - Data.Add("libgit2.category", (int)category); - - } - - private static string FormatMessage(string message, GitErrorCode code, GitErrorCategory category) - { - return String.Format(CultureInfo.InvariantCulture, "An error was raised by libgit2. Category = {0} ({1}).{2}{3}", - category, - code, - Environment.NewLine, - message); - } + { } +#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 new file mode 100644 index 000000000..b38f40496 --- /dev/null +++ b/LibGit2Sharp/LockedFileException.cs @@ -0,0 +1,72 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown attempting to open a locked file. + /// +#if NETFRAMEWORK + [Serializable] +#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. + /// + /// 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. + /// + /// 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 LockedFileException(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 LockedFileException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal LockedFileException(string message, GitErrorCategory category) + : base(message, 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/MatchedPathsAggregator.cs b/LibGit2Sharp/MatchedPathsAggregator.cs new file mode 100644 index 000000000..ecae6f2c7 --- /dev/null +++ b/LibGit2Sharp/MatchedPathsAggregator.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + internal class MatchedPathsAggregator : IEnumerable + { + private readonly List matchedPaths = new List(); + + /// + /// The delegate with a signature that matches the native diff git_diff_notify_cb function's signature. + /// + /// The diff list so far, before the delta is inserted. + /// The delta that is being diffed + /// The pathsec that matched the path of the diffed files. + /// Payload object. + internal int OnGitDiffNotify(IntPtr diffListSoFar, IntPtr deltaToAdd, IntPtr matchedPathspec, IntPtr payload) + { + // Convert null strings into empty strings. + var path = LaxFilePathMarshaler.FromNative(matchedPathspec) ?? FilePath.Empty; + + if (matchedPaths.Contains(path)) + { + return 0; + } + + matchedPaths.Add(path); + return 0; + } + + public IEnumerator GetEnumerator() + { + return matchedPaths.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} 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 7ee86b008..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 new file mode 100644 index 000000000..d7d761c1d --- /dev/null +++ b/LibGit2Sharp/MergeFetchHeadNotFoundException.cs @@ -0,0 +1,59 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#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. + /// + /// 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. + /// + /// 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 MergeFetchHeadNotFoundException(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 MergeFetchHeadNotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/MergeHead.cs b/LibGit2Sharp/MergeHead.cs index 4e0076cab..b548b254a 100644 --- a/LibGit2Sharp/MergeHead.cs +++ b/LibGit2Sharp/MergeHead.cs @@ -3,23 +3,22 @@ namespace LibGit2Sharp { /// - /// A merge head is a parent for the next commit. + /// A merge head is a parent for the next commit. /// - public class MergeHead : ReferenceWrapper + internal class MergeHead : ReferenceWrapper { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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. + /// Gets the that this merge head points to. /// public virtual Commit Tip { @@ -27,7 +26,7 @@ public virtual Commit Tip } /// - /// Returns "MERGE_HEAD[i]", where i is the index of this merge head. + /// Returns "MERGE_HEAD[i]", where i is the index of this merge head. /// protected override string Shorten() { diff --git a/LibGit2Sharp/MergeOptions.cs b/LibGit2Sharp/MergeOptions.cs new file mode 100644 index 000000000..b57d955e4 --- /dev/null +++ b/LibGit2Sharp/MergeOptions.cs @@ -0,0 +1,50 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Options controlling Merge behavior. + /// + public sealed class MergeOptions : MergeAndCheckoutOptionsBase + { + /// + /// 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 MergeOptions() + { } + + /// + /// The type of merge to perform. + /// + public FastForwardStrategy FastForwardStrategy { get; set; } + } + + /// + /// Strategy used for merging. + /// + public enum FastForwardStrategy + { + /// + /// Default fast-forward strategy. If the merge.ff configuration option is set, + /// it will be used. If it is not set, this will perform a fast-forward merge if + /// possible, otherwise a non-fast-forward merge that results in a merge commit. + /// + Default = 0, + + /// + /// Do not fast-forward. Always creates a merge commit. + /// + NoFastForward = 1, /* GIT_MERGE_NO_FASTFORWARD */ + + /// + /// Only perform fast-forward merges. + /// + FastForwardOnly = 2, /* GIT_MERGE_FASTFORWARD_ONLY */ + } +} 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 new file mode 100644 index 000000000..6c850c639 --- /dev/null +++ b/LibGit2Sharp/MergeResult.cs @@ -0,0 +1,66 @@ +namespace LibGit2Sharp +{ + /// + /// Class to report the result of a merge. + /// + public class MergeResult + { + /// + /// Needed for mocking purposes. + /// + protected MergeResult() + { } + + internal MergeResult(MergeStatus status, Commit commit = null) + { + this.Status = status; + this.Commit = commit; + } + + /// + /// The status of the merge. + /// + public virtual MergeStatus Status + { + get; + private set; + } + + /// + /// The resulting commit of the merge. For fast-forward merges, this is the + /// commit that merge was fast forwarded to. + /// This will return null if the merge has been unsuccessful due to conflicts. + /// + public virtual Commit Commit + { + get; + private set; + } + } + + /// + /// The status of what happened as a result of a merge. + /// + public enum MergeStatus + { + /// + /// Merge was up-to-date. + /// + UpToDate, + + /// + /// Fast-forward merge. + /// + FastForward, + + /// + /// Non-fast-forward merge. + /// + NonFastForward, + + /// + /// Merge resulted in conflicts. + /// + Conflicts, + } +} 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/Mode.cs b/LibGit2Sharp/Mode.cs index d164956d5..b2ade9dde 100644 --- a/LibGit2Sharp/Mode.cs +++ b/LibGit2Sharp/Mode.cs @@ -1,49 +1,49 @@ namespace LibGit2Sharp { /// - /// Git specific modes for entries. + /// Git specific modes for entries. /// public enum Mode { // Inspired from http://stackoverflow.com/a/8347325/335418 /// - /// 000000 file mode (the entry doesn't exist) + /// 000000 file mode (the entry doesn't exist or is unreadable) /// Nonexistent = 0, /// - /// 040000 file mode + /// 040000 file mode /// Directory = 0x4000, /// - /// 100644 file mode + /// 100644 file mode /// NonExecutableFile = 0x81A4, /// - /// Obsolete 100664 file mode. - /// 0100664 mode is an early Git design mistake. It's kept for - /// ascendant compatibility as some and - /// entries may still bear - /// this mode in some old git repositories, but it's now deprecated. - /// + /// Obsolete 100664 file mode. + /// 0100664 mode is an early Git design mistake. It's kept for + /// ascendant compatibility as some and + /// entries may still bear + /// this mode in some old git repositories, but it's now deprecated. + /// /// NonExecutableGroupWritableFile = 0x81B4, /// - /// 100755 file mode + /// 100755 file mode /// ExecutableFile = 0x81ED, /// - /// 120000 file mode + /// 120000 file mode /// SymbolicLink = 0xA000, /// - /// 160000 file mode + /// 160000 file mode /// GitLink = 0xE000 } diff --git a/LibGit2Sharp/NameConflictException.cs b/LibGit2Sharp/NameConflictException.cs index 989256ca2..0517f2550 100644 --- a/LibGit2Sharp/NameConflictException.cs +++ b/LibGit2Sharp/NameConflictException.cs @@ -1,54 +1,72 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// The exception that is thrown when a reference, a remote, a submodule... with the same name already exists in the repository + /// 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. + /// Initializes a new instance of the class. /// public NameConflictException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 NameConflictException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 c45c8ee5f..ba0a33144 100644 --- a/LibGit2Sharp/Network.cs +++ b/LibGit2Sharp/Network.cs @@ -1,16 +1,16 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// Provides access to network functionality for a repository. + /// Provides access to network functionality for a repository. /// public class Network { @@ -18,7 +18,7 @@ public class Network private readonly Lazy remotes; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Network() { } @@ -30,224 +30,461 @@ internal Network(Repository repository) } /// - /// The heads that have been updated during the last fetch. + /// Lookup and manage remotes in the repository. /// - public virtual IEnumerable FetchHeads + public virtual RemoteCollection Remotes { - get - { - int i = 0; + get { return remotes.Value; } + } - return Proxy.git_repository_fetchhead_foreach( - repository.Handle, - (name, url, oid, isMerge) => new FetchHead(repository, name, url, oid, isMerge, i++)); - } + /// + /// 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 references in the repository. + public virtual IEnumerable ListReferences(Remote remote) + { + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, null, new ProxyOptions()); } /// - /// Lookup and manage remotes in the repository. + /// 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. + /// /// - public virtual RemoteCollection Remotes + /// The to list from. + /// Options for connecting through a proxy. + /// The references in the repository. + public virtual IEnumerable ListReferences(Remote remote, ProxyOptions proxyOptions) { - get { return remotes.Value; } + Ensure.ArgumentNotNull(remote, "remote"); + + return ListReferencesInternal(remote.Url, null, proxyOptions); } /// - /// List references in a repository. + /// 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 references in the repository. - public virtual IEnumerable ListReferences(Remote remote) + /// 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); + } + + /// + /// 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 references in the remote repository. + public virtual IEnumerable ListReferences(string url) + { + Ensure.ArgumentNotNull(url, "url"); + + return ListReferencesInternal(url, null, new ProxyOptions()); + } + + /// + /// 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) + { + Ensure.ArgumentNotNull(url, "url"); + + return ListReferencesInternal(url, null, proxyOptions); + } + + /// + /// 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"); + + return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + } - List directReferences = new List(); - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) + /// + /// 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. + /// Options for connecting through a proxy. + /// The references in the remote repository. + public virtual IEnumerable ListReferences(string url, CredentialsHandler credentialsProvider, ProxyOptions proxyOptions) + { + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(credentialsProvider, "credentialsProvider"); + + return ListReferencesInternal(url, credentialsProvider, new ProxyOptions()); + } + + 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) { - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); + 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 a url with a set of fetch refspecs + /// + /// The url to fetch from + /// The list of resfpecs to use + public virtual void Fetch(string url, IEnumerable refspecs) + { + Fetch(url, refspecs, null, null); + } - NativeMethods.git_headlist_cb cb = (ref GitRemoteHead remoteHead, IntPtr payload) => + /// + /// 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); + } + + /// + /// 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); + } + + /// + /// Fetch from a url with a set of fetch refspecs + /// + /// The url to fetch from + /// The list of resfpecs to use + /// controlling fetch behavior + /// Message to use when updating the reflog. + public virtual void Fetch( + string url, + IEnumerable refspecs, + FetchOptions options, + string logMessage) + { + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(refspecs, "refspecs"); + + 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) + { + if (string.IsNullOrEmpty(branch.UpstreamBranchCanonicalName)) { - // The name pointer should never be null - if it is, - // this indicates a bug somewhere (libgit2, server, etc). - if (remoteHead.NamePtr == IntPtr.Zero) - { - Proxy.giterr_set_str(GitErrorCategory.Invalid, "Not expecting null value for reference name."); - return -1; - } - - ObjectId oid = remoteHead.Oid; - string name = Utf8Marshaler.FromNative(remoteHead.NamePtr); - directReferences.Add(new DirectReference(name, this.repository, oid)); - - return 0; - }; - - Proxy.git_remote_ls(remoteHandle, cb); + throw new LibGit2SharpException("The branch '{0}' (\"{1}\") that you are trying to push does not track an upstream branch.", + branch.FriendlyName, branch.CanonicalName); + } } - return directReferences; + 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 . + /// Push the objectish to the destination reference on the . /// - /// The to push to. + /// The to push to. /// The source objectish to push. /// The reference to update on the remote. - /// Handler for reporting failed push updates. - /// Credentials to use for user/pass authentication public virtual void Push( Remote remote, string objectish, - string destinationSpec, - PushStatusErrorHandler onPushStatusError, - Credentials credentials = null) + string destinationSpec) { - Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(objectish, "objectish"); - Ensure.ArgumentNotNullOrEmptyString(destinationSpec, destinationSpec); + Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); - Push(remote, string.Format(CultureInfo.InvariantCulture, - "{0}:{1}", objectish, destinationSpec), onPushStatusError, credentials); + Push(remote, + string.Format(CultureInfo.InvariantCulture, + "{0}:{1}", + objectish, + destinationSpec)); } /// - /// Push specified reference to the . + /// 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. + /// controlling push behavior + public virtual void Push( + Remote remote, + string objectish, + string destinationSpec, + PushOptions pushOptions) + { + Ensure.ArgumentNotNull(objectish, "objectish"); + 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 to push to. /// The pushRefSpec to push. - /// Handler for reporting failed push updates. - /// Credentials to use for user/pass authentication + /// controlling push behavior public virtual void Push( Remote remote, string pushRefSpec, - PushStatusErrorHandler onPushStatusError, - Credentials credentials = null) + PushOptions pushOptions) { - Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); - Push(remote, new string[] { pushRefSpec }, onPushStatusError, credentials); + Push(remote, new[] { pushRefSpec }, pushOptions); } /// - /// Push specified references to the . + /// Push specified references to the . /// - /// The to push to. + /// The to push to. /// The pushRefSpecs to push. - /// Handler for reporting failed push updates. - /// Credentials to use for user/pass authentication - public virtual void Push( - Remote remote, - IEnumerable pushRefSpecs, - PushStatusErrorHandler onPushStatusError, - Credentials credentials = null) + public virtual void Push(Remote remote, IEnumerable pushRefSpecs) + { + Push(remote, pushRefSpecs, null); + } + + /// + /// Push specified references to the . + /// + /// The to push to. + /// The pushRefSpecs to push. + /// controlling push behavior + public virtual void Push(Remote remote, IEnumerable pushRefSpecs, PushOptions pushOptions) { Ensure.ArgumentNotNull(remote, "remote"); Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); - // We need to keep a reference to the git_cred_acquire_cb callback around - // so it will not be garbage collected before we are done with it. - // Note that we also have a GC.KeepAlive call at the end of the method. - NativeMethods.git_cred_acquire_cb credentialCallback = null; - // Return early if there is nothing to push. if (!pushRefSpecs.Any()) { return; } - PushCallbacks pushStatusUpdates = new PushCallbacks(onPushStatusError); - - // Load the remote. - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, remote.Name, true)) + if (pushOptions == null) { - if (credentials != null) - { - credentialCallback = (out IntPtr cred, IntPtr url, IntPtr username_from_url, uint types, IntPtr payload) => - NativeMethods.git_cred_userpass_plaintext_new(out cred, credentials.Username, credentials.Password); - - Proxy.git_remote_set_cred_acquire_cb( - remoteHandle, - credentialCallback, - IntPtr.Zero); - } - - try - { - Proxy.git_remote_connect(remoteHandle, GitDirection.Push); - - // Perform the actual push. - using (PushSafeHandle pushHandle = Proxy.git_push_new(remoteHandle)) - { - // Add refspecs. - foreach (string pushRefSpec in pushRefSpecs) - { - Proxy.git_push_add_refspec(pushHandle, pushRefSpec); - } + pushOptions = new PushOptions(); + } - Proxy.git_push_finish(pushHandle); + // Load the remote. + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, remote.Name, true)) - if (!Proxy.git_push_unpack_ok(pushHandle)) - { - throw new LibGit2SharpException("Push failed - remote did not successfully unpack."); - } + // Create a git options wrapper so managed strings are disposed. + using (var pushOptionsWrapper = new GitPushOptionsWrapper()) + { + var callbacks = new RemoteCallbacks(pushOptions); + GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - Proxy.git_push_status_foreach(pushHandle, pushStatusUpdates.Callback); + var gitPushOptions = pushOptionsWrapper.Options; + gitPushOptions.PackbuilderDegreeOfParallelism = pushOptions.PackbuilderDegreeOfParallelism; + gitPushOptions.RemoteCallbacks = gitCallbacks; + gitPushOptions.ProxyOptions = pushOptions.ProxyOptions.CreateGitProxyOptions(); - Proxy.git_push_update_tips(pushHandle); - } - } - finally + // 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); } - } - // To be safe, make sure the credential callback is kept until - // alive until at least this point. - GC.KeepAlive(credentialCallback); + Proxy.git_remote_push(remoteHandle, + pushRefSpecs, + gitPushOptions); + } } /// - /// Helper class to handle callbacks during push. + /// The heads that have been updated during the last fetch. /// - private class PushCallbacks + internal virtual IEnumerable FetchHeads { - readonly PushStatusErrorHandler onError; - - public PushCallbacks(PushStatusErrorHandler onError) - { - this.onError = onError; - } - - public int Callback(IntPtr referenceNamePtr, IntPtr msgPtr, IntPtr payload) + get { - // 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; - } + int i = 0; - // Only report updates where there is a message - indicating - // that there was an error. - if (msgPtr != IntPtr.Zero) - { - string referenceName = Utf8Marshaler.FromNative(referenceNamePtr); - string msg = Utf8Marshaler.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 3dfdddfbc..000000000 --- a/LibGit2Sharp/NetworkExtensions.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Globalization; -using LibGit2Sharp.Core; -using System.Collections.Generic; - -namespace LibGit2Sharp -{ - /// - /// Provides helper overloads to a . - /// - public static class NetworkExtensions - { - /// - /// Push the objectish to the destination reference on the . - /// - /// The being worked with. - /// The to push to. - /// The source objectish to push. - /// The reference to update on the remote. - /// Credentials to use for user/pass authentication - /// Results of the push operation. - public static PushResult Push( - this Network network, - Remote remote, - string objectish, - string destinationSpec, - Credentials credentials = null) - { - Ensure.ArgumentNotNull(remote, "remote"); - Ensure.ArgumentNotNull(objectish, "objectish"); - Ensure.ArgumentNotNullOrEmptyString(destinationSpec, "destinationSpec"); - - return network.Push(remote, string.Format(CultureInfo.InvariantCulture, - "{0}:{1}", objectish, destinationSpec), credentials); - } - - /// - /// Push specified reference to the . - /// - /// The being worked with. - /// The to push to. - /// The pushRefSpec to push. - /// Credentials to use for user/pass authentication - /// Results of the push operation. - public static PushResult Push(this Network network, Remote remote, string pushRefSpec, Credentials credentials = null) - { - Ensure.ArgumentNotNull(remote, "remote"); - Ensure.ArgumentNotNullOrEmptyString(pushRefSpec, "pushRefSpec"); - - return network.Push(remote, new string[] { pushRefSpec }, credentials); - } - - /// - /// Push specified references to the . - /// - /// The being worked with. - /// The to push to. - /// The pushRefSpecs to push. - /// Credentials to use for user/pass authentication - /// Results of the push operation. - public static PushResult Push(this Network network, Remote remote, IEnumerable pushRefSpecs, Credentials credentials = null) - { - Ensure.ArgumentNotNull(remote, "remote"); - Ensure.ArgumentNotNull(pushRefSpecs, "pushRefSpecs"); - - List failedRemoteUpdates = new List(); - - network.Push( - remote, - pushRefSpecs, - failedRemoteUpdates.Add, - credentials); - - return new PushResult(failedRemoteUpdates); - } - } -} diff --git a/LibGit2Sharp/NonFastForwardException.cs b/LibGit2Sharp/NonFastForwardException.cs index 7f28045cb..d8ed8f474 100644 --- a/LibGit2Sharp/NonFastForwardException.cs +++ b/LibGit2Sharp/NonFastForwardException.cs @@ -1,55 +1,73 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// The exception that is thrown when push cannot be performed - /// against the remote without losing commits. + /// 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. + /// Initializes a new instance of the class. /// public NonFastForwardException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 NonFastForwardException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 new file mode 100644 index 000000000..f282c4340 --- /dev/null +++ b/LibGit2Sharp/NotFoundException.cs @@ -0,0 +1,72 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown attempting to reference a resource that does not exist. + /// +#if NETFRAMEWORK + [Serializable] +#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. + /// + /// 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. + /// + /// 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 NotFoundException(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 NotFoundException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal NotFoundException(string message, GitErrorCategory category) + : base(message, category) + { } + + internal override GitErrorCode ErrorCode + { + get + { + return GitErrorCode.NotFound; + } + } + } +} diff --git a/LibGit2Sharp/Note.cs b/LibGit2Sharp/Note.cs index 321ebc8a2..2ffc89690 100644 --- a/LibGit2Sharp/Note.cs +++ b/LibGit2Sharp/Note.cs @@ -7,13 +7,16 @@ namespace LibGit2Sharp { /// - /// A note, attached to a given . + /// A note, attached to a given . /// [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. + /// Needed for mocking purposes. /// protected Note() { } @@ -27,59 +30,56 @@ private Note(ObjectId blobId, string message, ObjectId targetObjectId, string @n } /// - /// The of the blob containing the note message. + /// The of the blob containing the note message. /// public virtual ObjectId BlobId { get; private set; } /// - /// The message. + /// The message. /// public virtual string Message { get; private set; } /// - /// The namespace with which this note is associated. - /// This is the abbreviated namespace (e.g.: commits), and not the canonical namespace (e.g.: refs/notes/commits). + /// The namespace with which this note is associated. + /// This is the abbreviated namespace (e.g.: commits), and not the canonical namespace (e.g.: refs/notes/commits). /// public virtual string Namespace { get; private set; } /// - /// The of the target object. + /// The of the target object. /// 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_oid(note); + ObjectId oid = Proxy.git_note_id(note); string message = Proxy.git_note_message(note); 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); } /// - /// 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 bool Equals(Note other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -88,10 +88,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(Note left, Note right) { @@ -99,10 +99,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(Note left, Note right) { @@ -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 5341f0595..30084881d 100644 --- a/LibGit2Sharp/NoteCollection.cs +++ b/LibGit2Sharp/NoteCollection.cs @@ -5,24 +5,21 @@ using System.Globalization; using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// A collection of exposed in the . + /// A collection of exposed in the . /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class NoteCollection : IEnumerable { - private readonly Repository repo; + internal readonly Repository repo; private readonly Lazy defaultNamespace; - private const string refsNotesPrefix = "refs/notes/"; - /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected NoteCollection() { } @@ -36,18 +33,18 @@ internal NoteCollection(Repository repo) #region Implementation of IEnumerable /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return this[DefaultNamespace].GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -56,7 +53,7 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// The default namespace for notes. + /// The default namespace for notes. /// public virtual string DefaultNamespace { @@ -64,30 +61,25 @@ public virtual string DefaultNamespace } /// - /// The list of canonicalized namespaces related to notes. + /// The list of canonicalized namespaces related to notes. /// public virtual IEnumerable Namespaces { - get - { - return NamespaceRefs.Select(UnCanonicalizeName); - } + get { return NamespaceRefs.Select(UnCanonicalizeName); } } internal IEnumerable NamespaceRefs { get { - return new[] { NormalizeToCanonicalName(DefaultNamespace) }.Concat( - from reference in repo.Refs - select reference.CanonicalName into refCanonical - where refCanonical.StartsWith(refsNotesPrefix, StringComparison.Ordinal) && refCanonical != NormalizeToCanonicalName(DefaultNamespace) - select refCanonical); + return new[] { NormalizeToCanonicalName(DefaultNamespace) }.Concat(repo.Refs + .Select(reference => reference.CanonicalName) + .Where(refCanonical => refCanonical.StartsWith(Reference.NotePrefix, StringComparison.Ordinal) && refCanonical != NormalizeToCanonicalName(DefaultNamespace))); } } /// - /// Gets the collection of associated with the specified . + /// Gets the collection of associated with the specified . /// public virtual IEnumerable this[ObjectId id] { @@ -96,14 +88,14 @@ public virtual IEnumerable this[ObjectId id] Ensure.ArgumentNotNull(id, "id"); return NamespaceRefs - .Select(ns => RetrieveNote(id, ns)) + .Select(ns => this[ns, id]) .Where(n => n != null); } } /// - /// Gets the collection of associated with the specified namespace. - /// This is similar to the 'get notes list' command. + /// Gets the collection of associated with the specified namespace. + /// This is similar to the 'get notes list' command. /// public virtual IEnumerable this[string @namespace] { @@ -113,17 +105,30 @@ public virtual IEnumerable this[string @namespace] string canonicalNamespace = NormalizeToCanonicalName(@namespace); - return Proxy.git_note_foreach(repo.Handle, canonicalNamespace, - (blobId,annotatedObjId) => RetrieveNote(annotatedObjId, canonicalNamespace)); + return Proxy.git_note_foreach(repo.Handle, + canonicalNamespace, + (blobId, annotatedObjId) => this[canonicalNamespace, annotatedObjId]); } } - internal Note RetrieveNote(ObjectId targetObjectId, string canonicalNamespace) + /// + /// Gets the associated with the specified objectId and the specified namespace. + /// + public virtual Note this[string @namespace, ObjectId id] { - using (NoteSafeHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, targetObjectId)) + get { - return noteHandle == null ? null : - Note.BuildFromPtr(noteHandle, UnCanonicalizeName(canonicalNamespace), targetObjectId); + Ensure.ArgumentNotNull(id, "id"); + Ensure.ArgumentNotNull(@namespace, "@namespace"); + + string canonicalNamespace = NormalizeToCanonicalName(@namespace); + + using (NoteHandle noteHandle = Proxy.git_note_read(repo.Handle, canonicalNamespace, id)) + { + return noteHandle == null + ? null + : Note.BuildFromPtr(noteHandle, UnCanonicalizeName(canonicalNamespace), id); + } } } @@ -138,34 +143,34 @@ internal static string NormalizeToCanonicalName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - if (name.StartsWith(refsNotesPrefix, StringComparison.Ordinal)) + if (name.LooksLikeNote()) { return name; } - return string.Concat(refsNotesPrefix, name); + return string.Concat(Reference.NotePrefix, name); } - internal string UnCanonicalizeName(string name) + internal static string UnCanonicalizeName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - if (!name.StartsWith(refsNotesPrefix, StringComparison.Ordinal)) + if (!name.LooksLikeNote()) { return name; } - return name.Substring(refsNotesPrefix.Length); + return name.Substring(Reference.NotePrefix.Length); } /// - /// Creates or updates a on the specified object, and for the given namespace. + /// Creates or updates a on the specified object, and for the given namespace. /// - /// The target , for which the note will be created. - /// The note message. - /// The author. - /// The committer. - /// 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 target , for which the note will be created. + /// The note message. + /// The author. + /// The committer. + /// 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 virtual Note Add(ObjectId targetId, string message, Signature author, Signature committer, string @namespace) { @@ -179,33 +184,18 @@ 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 RetrieveNote(targetId, canonicalNamespace); + return this[canonicalNamespace, targetId]; } /// - /// Creates or updates a on the specified object, and for the given namespace. + /// Deletes the note on the specified object, and for the given namespace. /// - /// The target , for which the note will be created. - /// The note message. - /// The author. - /// The committer. - /// 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. - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Note Create(ObjectId targetId, string message, Signature author, Signature committer, string @namespace) - { - return Add(targetId, message, author, committer, @namespace); - } - - /// - /// Deletes the note on the specified object, and for the given namespace. - /// - /// The target , for which the note will be created. - /// The author. - /// The committer. - /// 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'). + /// The target , for which the note will be created. + /// The author. + /// The committer. + /// 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 virtual void Remove(ObjectId targetId, Signature author, Signature committer, string @namespace) { Ensure.ArgumentNotNull(targetId, "targetId"); @@ -218,25 +208,11 @@ public virtual void Remove(ObjectId targetId, Signature author, Signature commit Proxy.git_note_remove(repo.Handle, canonicalNamespace, author, committer, targetId); } - /// - /// Deletes the note on the specified object, and for the given namespace. - /// - /// The target , for which the note will be created. - /// The author. - /// The committer. - /// 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'). - [Obsolete("This method will be removed in the next release. Please use Remove() instead.")] - public virtual void Delete(ObjectId targetId, Signature author, Signature committer, string @namespace) - { - Remove(targetId, author, committer, @namespace); - } - 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/ObjectDatabase.cs b/LibGit2Sharp/ObjectDatabase.cs index 7db7502be..1bad9c907 100644 --- a/LibGit2Sharp/ObjectDatabase.cs +++ b/LibGit2Sharp/ObjectDatabase.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -10,16 +11,16 @@ namespace LibGit2Sharp { /// - /// Provides methods to directly work against the Git object database - /// without involving the index nor the working directory. + /// Provides methods to directly work against the Git object database + /// without involving the index nor the working directory. /// - public class ObjectDatabase + public class ObjectDatabase : IEnumerable { private readonly Repository repo; - private readonly ObjectDatabaseSafeHandle handle; + private readonly ObjectDatabaseHandle handle; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected ObjectDatabase() { } @@ -32,8 +33,34 @@ internal ObjectDatabase(Repository repo) repo.RegisterForCleanup(handle); } + #region Implementation of IEnumerable + /// - /// Determines if the given object can be found in the object database. + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + ICollection oids = Proxy.git_odb_foreach(handle); + + return oids + .Select(gitOid => repo.Lookup(gitOid)) + .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(); + } + + #endregion + + /// + /// Determines if the given object can be found in the object database. /// /// Identifier of the object being searched for. /// True if the object has been found; false otherwise. @@ -45,11 +72,25 @@ public virtual bool Contains(ObjectId objectId) } /// - /// Inserts a into the object database, created from the content of a file. + /// 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. /// /// Path to the file to create the blob from. A relative path is allowed to - /// be passed if the is a standard, non-bare, repository. The path - /// will then be considered as a path relative to the root of the working directory. + /// be passed if the is a standard, non-bare, repository. The path + /// will then be considered as a path relative to the root of the working directory. /// The created . public virtual Blob CreateBlob(string path) { @@ -57,20 +98,24 @@ 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); } /// - /// Adds the provided backend to the object database with the specified priority. + /// Adds the provided backend to the object database with the specified priority. + /// + /// If the provided backend implements , the + /// method will be honored and invoked upon the disposal of the repository. + /// /// /// The backend to add /// The priority at which libgit2 should consult this backend (higher values are consulted first) @@ -79,22 +124,52 @@ public virtual void AddBackend(OdbBackend backend, int priority) Ensure.ArgumentNotNull(backend, "backend"); Ensure.ArgumentConformsTo(priority, s => s > 0, "priority"); - Proxy.git_odb_add_backend(this.handle, backend.GitOdbBackendPointer, priority); + Proxy.git_odb_add_backend(handle, backend.GitOdbBackendPointer, priority); } private class Processor { - private readonly BinaryReader _reader; + private readonly Stream stream; + private readonly long? numberOfBytesToConsume; + private int totalNumberOfReadBytes; - public Processor(BinaryReader reader) + public Processor(Stream stream, long? numberOfBytesToConsume) { - _reader = reader; + this.stream = stream; + this.numberOfBytesToConsume = numberOfBytesToConsume; } public int Provider(IntPtr content, int max_length, IntPtr data) { var local = new byte[max_length]; - int numberOfReadBytes = _reader.Read(local, 0, max_length); + + int bytesToRead = max_length; + + if (numberOfBytesToConsume.HasValue) + { + long totalRemainingBytesToRead = numberOfBytesToConsume.Value - totalNumberOfReadBytes; + + if (totalRemainingBytesToRead < max_length) + { + bytesToRead = totalRemainingBytesToRead > int.MaxValue + ? int.MaxValue + : (int)totalRemainingBytesToRead; + } + } + + if (bytesToRead == 0) + { + return 0; + } + + int numberOfReadBytes = stream.Read(local, 0, bytesToRead); + + if (numberOfBytesToConsume.HasValue && numberOfReadBytes == 0) + { + return (int)GitErrorCode.User; + } + + totalNumberOfReadBytes += numberOfReadBytes; Marshal.Copy(local, 0, content, numberOfReadBytes); @@ -103,26 +178,170 @@ public int Provider(IntPtr content, int max_length, IntPtr data) } /// - /// Inserts a into the object database, created from the content of a data provider. + /// Writes an object to the object database. + /// + /// 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 reader that will provide the content of the blob to be created. + /// 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"); + + if (!stream.CanRead) + { + throw new ArgumentException("The stream cannot be read from.", nameof(stream)); + } + + using (var odbStream = Proxy.git_odb_open_wstream(handle, numberOfBytesToConsume, GitObject.TypeToGitKindMap[typeof(T)])) + { + 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); + } + } + + /// + /// 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); + } + + /// + /// 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(BinaryReader reader, string hintpath = null) + public virtual Blob CreateBlob(Stream stream, string hintpath) + { + return CreateBlob(stream, hintpath, null); + } + + /// + /// 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 number of bytes to consume from the stream. + /// The created . + 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(reader, "reader"); + Ensure.ArgumentNotNull(stream, "stream"); - var proc = new Processor(reader); - ObjectId id = Proxy.git_blob_create_fromchunks(repo.Handle, hintpath, proc.Provider); + // 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.", nameof(stream)); + } + + 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]; + long totalRead = 0; + int read = 0; + + while (true) + { + 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) + { + break; + } + + fixed (byte* buffer_ptr = buffer) + { + writestream.write(writestream_ptr, (IntPtr)buffer_ptr, (UIntPtr)read); + } + + totalRead += read; + } + + 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); } /// - /// Inserts a into the object database, created from a . + /// Inserts a into the object database, created from a . /// - /// The . - /// The created . + /// The . + /// The created . public virtual Tree CreateTree(TreeDefinition treeDefinition) { Ensure.ArgumentNotNull(treeDefinition, "treeDefinition"); @@ -131,33 +350,743 @@ public virtual Tree CreateTree(TreeDefinition treeDefinition) } /// - /// Inserts a into the object database, referencing an existing . + /// Inserts a into the object database, created from the . + /// + /// It recursively creates tree objects for each of the subtrees stored in the index, but only returns the root tree. + /// + /// + /// The index must be fully merged. + /// /// - /// 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 of the to be created. - /// The parents of the to be created. - /// The created . - public virtual Commit CreateCommit(string message, Signature author, Signature committer, Tree tree, IEnumerable parents) + /// The . + /// The created . This can be used e.g. to create a . + public virtual Tree CreateTree(Index index) { - return CreateCommit(message, author, committer, tree, parents, null); + Ensure.ArgumentNotNull(index, "index"); + + var treeId = Proxy.git_index_write_tree(index.Handle); + return this.repo.Lookup(treeId); } - internal Commit CreateCommit(string message, Signature author, Signature committer, Tree tree, IEnumerable parents, string referenceName) + /// + /// 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 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. + /// The created . + 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"); Ensure.ArgumentNotNull(author, "author"); Ensure.ArgumentNotNull(committer, "committer"); Ensure.ArgumentNotNull(tree, "tree"); Ensure.ArgumentNotNull(parents, "parents"); - string prettifiedMessage = Proxy.git_message_prettify(message); - IEnumerable parentIds = parents.Select(p => p.Id); + if (prettifyMessage) + { + message = Proxy.git_message_prettify(message, commentChar); + } + GitOid[] parentIds = parents.Select(p => p.Id.Oid).ToArray(); + + ObjectId commitId = Proxy.git_commit_create(repo.Handle, null, author, committer, message, tree, parentIds); + + 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); + } + + /// + /// Inserts a into the object database, pointing to a specific . + /// + /// The name. + /// The being pointed at. + /// The tagger. + /// The message. + /// The created . + public virtual TagAnnotation CreateTagAnnotation(string name, GitObject target, Signature tagger, string message) + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNull(message, "message"); + Ensure.ArgumentNotNull(target, "target"); + Ensure.ArgumentNotNull(tagger, "tagger"); + Ensure.ArgumentDoesNotContainZeroByte(name, "name"); + Ensure.ArgumentDoesNotContainZeroByte(message, "message"); + + string prettifiedMessage = Proxy.git_message_prettify(message, null); + + ObjectId tagId = Proxy.git_tag_annotation_create(repo.Handle, name, target, tagger, prettifiedMessage); + + 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. + /// + /// The commit. + /// The archiver to use. + public virtual void Archive(Commit commit, ArchiverBase archiver) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(archiver, "archiver"); + + archiver.OrchestrateArchiving(commit.Tree, commit.Id, commit.Committer.When); + } + + /// + /// Archive the given tree. + /// + /// The tree. + /// The archiver to use. + public virtual void Archive(Tree tree, ArchiverBase archiver) + { + Ensure.ArgumentNotNull(tree, "tree"); + Ensure.ArgumentNotNull(archiver, "archiver"); + + archiver.OrchestrateArchiving(tree, null, DateTimeOffset.UtcNow); + } + + /// + /// Returns the merge base (best common ancestor) of the given commits + /// and the distance between each of these commits and this base. + /// + /// The being used as a reference. + /// The being compared against . + /// A instance of . + public virtual HistoryDivergence CalculateHistoryDivergence(Commit one, Commit another) + { + Ensure.ArgumentNotNull(one, "one"); + Ensure.ArgumentNotNull(another, "another"); + + 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)); + } - ObjectId commitId = Proxy.git_commit_create(repo.Handle, referenceName, author, committer, prettifiedMessage, tree, parentIds); + 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 . + /// + /// 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) + { + Ensure.ArgumentNotNull(gitObject, "gitObject"); + + if (minLength <= 0 || minLength > 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 (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); + } + + /// + /// 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; + } - return repo.Lookup(commitId); + 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/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 08fa11293..d87bbcb34 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -6,18 +6,18 @@ namespace LibGit2Sharp { /// - /// Uniquely identifies a . + /// Uniquely identifies a . /// - public class ObjectId : IEquatable + public sealed class ObjectId : IEquatable { private readonly GitOid oid; - private const int rawSize = 20; + private const int rawSize = GitOid.Size; private readonly string sha; /// - /// Size of the string-based representation of a SHA-1. + /// Size of the string-based representation of a SHA-1. /// - protected const int HexSize = rawSize * 2; + internal const int HexSize = rawSize * 2; private const string hexDigits = "0123456789abcdef"; private static readonly byte[] reverseHexDigits = BuildReverseHexDigits(); @@ -27,24 +27,29 @@ public class ObjectId : IEquatable new LambdaEqualityHelper(x => x.Sha); /// - /// Zero ObjectId + /// Zero ObjectId /// public static ObjectId Zero = new ObjectId(new string('0', HexSize)); /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The oid. + /// The oid. 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), nameof(oid)); + } + this.oid = oid; - sha = ToString(oid.Id); + sha = ToString(oid.Id, oid.Id.Length * 2); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The byte array. + /// The byte array. public ObjectId(byte[] rawId) : this(new GitOid { Id = rawId }) { @@ -52,10 +57,36 @@ 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. + /// Initializes a new instance of the class. /// - /// The sha. + /// The sha. public ObjectId(string sha) { GitOid? parsedOid = BuildOidFrom(sha, true); @@ -75,7 +106,7 @@ internal GitOid Oid } /// - /// Gets the raw id. + /// Gets the raw id. /// public byte[] RawId { @@ -83,19 +114,19 @@ public byte[] RawId } /// - /// Gets the sha. + /// Gets the sha. /// - public virtual string Sha + public string Sha { get { return sha; } } /// - /// Converts the specified string representation of a Sha-1 to its equivalent and returns a value that indicates whether the conversion succeeded. + /// Converts the specified string representation of a Sha-1 to its equivalent and returns a value that indicates whether the conversion succeeded. /// - /// A string containing a Sha-1 to convert. - /// When this method returns, contains the value equivalent to the Sha-1 contained in , if the conversion succeeded, or null if the conversion failed. - /// true if the parameter was converted successfully; otherwise, false. + /// A string containing a Sha-1 to convert. + /// When this method returns, contains the value equivalent to the Sha-1 contained in , if the conversion succeeded, or null if the conversion failed. + /// true if the parameter was converted successfully; otherwise, false. public static bool TryParse(string sha, out ObjectId result) { result = BuildOidFrom(sha, false); @@ -114,27 +145,27 @@ 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); } /// - /// 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 bool Equals(ObjectId other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -143,19 +174,19 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// - /// The that represents the current . + /// The that represents the current . public override string ToString() { return Sha; } /// - /// 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 . + /// The number of chars the should be truncated to. + /// The that represents the current . public string ToString(int prefixLength) { int normalizedLength = NormalizeLength(prefixLength); @@ -178,10 +209,10 @@ private static int NormalizeLength(int prefixLength) } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(ObjectId left, ObjectId right) { @@ -189,16 +220,26 @@ private static int NormalizeLength(int prefixLength) } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(ObjectId left, ObjectId right) { return !Equals(left, right); } + /// + /// Create an for the given . + /// + /// The object SHA. + /// An , or null if is null. + public static explicit operator ObjectId(string sha) + { + return sha == null ? null : new ObjectId(sha); + } + private static byte[] BuildReverseHexDigits() { var bytes = new byte['f' - '0' + 1]; @@ -216,18 +257,13 @@ private static byte[] BuildReverseHexDigits() return bytes; } - private static string ToString(byte[] id) + internal static string ToString(byte[] id, int lengthInNibbles) { - if (id == null || id.Length != rawSize) - { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "A non null array of {0} bytes is expected.", rawSize), "id"); - } - // Inspired from http://stackoverflow.com/questions/623104/c-byte-to-hex-string/3974535#3974535 - var c = new char[HexSize]; + var c = new char[lengthInNibbles]; - for (int i = 0; i < HexSize; i++) + for (int i = 0; i < (lengthInNibbles & -2); i++) { int index0 = i >> 1; var b = ((byte)(id[index0] >> 4)); @@ -237,6 +273,13 @@ private static string ToString(byte[] id) c[i] = hexDigits[b]; } + if ((lengthInNibbles & 1) == 1) + { + int index0 = lengthInNibbles >> 1; + var b = ((byte)(id[index0] >> 4)); + c[lengthInNibbles - 1] = hexDigits[b]; + } + return new string(c); } @@ -280,12 +323,30 @@ 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); + } + + /// + /// Determine whether matches the hexified + /// representation of the first nibbles of this instance. + /// + /// Comparison is made in a case insensitive-manner. + /// + /// + /// True if this instance starts with , + /// false otherwise. + public bool StartsWith(string shortSha) + { + Ensure.ArgumentNotNullOrEmptyString(shortSha, "shortSha"); + + return Sha.StartsWith(shortSha, StringComparison.OrdinalIgnoreCase); } } } diff --git a/LibGit2Sharp/ObjectType.cs b/LibGit2Sharp/ObjectType.cs new file mode 100644 index 000000000..3e5d415fb --- /dev/null +++ b/LibGit2Sharp/ObjectType.cs @@ -0,0 +1,57 @@ +using System; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Underlying type of a + /// + public enum ObjectType + { + /// + /// A commit object. + /// + Commit = 1, + + /// + /// A tree (directory listing) object. + /// + Tree = 2, + + /// + /// A file revision object. + /// + Blob = 3, + + /// + /// An annotated tag object. + /// + Tag = 4, + } + + internal static class ObjectTypeExtensions + { + public static GitObjectType ToGitObjectType(this ObjectType type) + { + switch (type) + { + case ObjectType.Commit: + return GitObjectType.Commit; + + case ObjectType.Tree: + return GitObjectType.Tree; + + case ObjectType.Blob: + return GitObjectType.Blob; + + case ObjectType.Tag: + return GitObjectType.Tag; + + default: + throw new InvalidOperationException( + string.Format(CultureInfo.InvariantCulture, "Cannot map {0} to a GitObjectType.", type)); + } + } + } +} diff --git a/LibGit2Sharp/OdbBackend.cs b/LibGit2Sharp/OdbBackend.cs index 995b61a58..645d0ac5f 100644 --- a/LibGit2Sharp/OdbBackend.cs +++ b/LibGit2Sharp/OdbBackend.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.IO; using System.Runtime.InteropServices; using LibGit2Sharp.Core; @@ -6,112 +7,141 @@ namespace LibGit2Sharp { /// - /// Base class for all custom managed backends for the libgit2 object database (ODB). + /// Base class for all custom managed backends for the libgit2 object database (ODB). + /// + /// If the derived backend implements , the + /// method will be honored and invoked upon the disposal of the repository. + /// /// public abstract class OdbBackend { /// - /// Invoked by libgit2 when this backend is no longer needed. + /// Invoked by libgit2 when this backend is no longer needed. /// - protected virtual void Dispose() + internal void Free() { - if (IntPtr.Zero != nativeBackendPointer) + if (nativeBackendPointer == IntPtr.Zero) { - GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeBackendPointer, GitOdbBackend.GCHandleOffset)).Free(); - Marshal.FreeHGlobal(nativeBackendPointer); - nativeBackendPointer = IntPtr.Zero; + return; } + + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeBackendPointer, GitOdbBackend.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativeBackendPointer); + nativeBackendPointer = IntPtr.Zero; } /// - /// In your subclass, override this member to provide the list of actions your backend supports. + /// In your subclass, override this member to provide the list of actions your backend supports. + /// + protected abstract OdbBackendOperations SupportedOperations { get; } + + /// + /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in + /// which to return the object's data. /// - protected abstract OdbBackendOperations SupportedOperations + /// The bytes to be copied to the stream. + /// + /// A Stream already filled with the content of provided the byte array. + /// Do not dispose this object before returning it. + /// + protected UnmanagedMemoryStream AllocateAndBuildFrom(byte[] bytes) { - get; + var stream = Allocate(bytes.Length); + + stream.Write(bytes, 0, bytes.Length); + + return stream; } /// - /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in - /// which to return the object's data. + /// Call this method from your implementations of Read and ReadPrefix to allocate a buffer in + /// which to return the object's data. /// - /// Number of bytes to allocate - /// An Stream for you to write to and then return. Do not dispose this object before returning it. - protected unsafe Stream Allocate(long bytes) + /// Number of bytes to allocate + /// 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 (bytes < 0 || - (UIntPtr.Size == sizeof(int) && bytes > int.MaxValue)) + if (size < 0 || (UIntPtr.Size == sizeof(int) && size > int.MaxValue)) { - throw new ArgumentOutOfRangeException("bytes"); + throw new ArgumentOutOfRangeException(nameof(size)); } - IntPtr buffer = Proxy.git_odb_backend_malloc(this.GitOdbBackendPointer, new UIntPtr((ulong)bytes)); + IntPtr buffer = Proxy.git_odb_backend_malloc(this.GitOdbBackendPointer, new UIntPtr((ulong)size)); - return new UnmanagedMemoryStream((byte*)buffer, 0, bytes, FileAccess.ReadWrite); + return new UnmanagedMemoryStream((byte*)buffer, 0, size, FileAccess.ReadWrite); } /// - /// Requests that this backend read an object. + /// Requests that this backend read an object. /// - public abstract int Read(byte[] oid, - out Stream data, - out GitObjectType objectType); + public abstract int Read( + ObjectId id, + out UnmanagedMemoryStream data, + out ObjectType objectType); /// - /// Requests that this backend read an object. The object ID may not be complete (may be a prefix). + /// Requests that this backend read an object. The object ID may not be complete (may be a prefix). /// - public abstract int ReadPrefix(byte[] shortOid, - out byte[] oid, - out Stream data, - out GitObjectType objectType); + public abstract int ReadPrefix( + string shortSha, + out ObjectId oid, + out UnmanagedMemoryStream data, + out ObjectType objectType); /// - /// Requests that this backend read an object's header (length and object type) but not its contents. + /// Requests that this backend read an object's header (length and object type) but not its contents. /// - public abstract int ReadHeader(byte[] oid, + public abstract int ReadHeader( + ObjectId id, out int length, - out GitObjectType objectType); + out ObjectType objectType); /// - /// Requests that this backend write an object to the backing store. The backend may need to compute the object ID - /// and return it to the caller. + /// Requests that this backend write an object to the backing store. /// - public abstract int Write(byte[] oid, + public abstract int Write( + ObjectId id, Stream dataStream, long length, - GitObjectType objectType, - out byte[] finalOid); + ObjectType objectType); /// - /// Requests that this backend read an object. Returns a stream so that the caller can read the data in chunks. + /// Requests that this backend read an object. Returns a stream so that the caller can read the data in chunks. /// - public abstract int ReadStream(byte[] oid, + public abstract int ReadStream( + ObjectId id, out OdbBackendStream stream); /// - /// Requests that this backend write an object to the backing store. Returns a stream so that the caller can write - /// the data in chunks. + /// Requests that this backend write an object to the backing store. Returns a stream so that the caller can write + /// the data in chunks. /// - public abstract int WriteStream(long length, - GitObjectType objectType, + public abstract int WriteStream( + long length, + ObjectType objectType, out OdbBackendStream stream); /// - /// Requests that this backend check if an object ID exists. + /// Requests that this backend check if an object ID exists. /// - public abstract bool Exists(byte[] oid); + public abstract bool Exists(ObjectId id); /// - /// Requests that this backend enumerate all items in the backing store. + /// Requests that this backend check if an object ID exists. The object ID may not be complete (may be a prefix). + /// + public abstract int ExistsPrefix(string shortSha, out ObjectId found); + + /// + /// Requests that this backend enumerate all items in the backing store. /// public abstract int ForEach(ForEachCallback callback); /// - /// The signature of the callback method provided to the Foreach method. + /// The signature of the callback method provided to the Foreach method. /// /// The object ID of the object in the backing store. /// A non-negative result indicates the enumeration should continue. Otherwise, the enumeration should stop. - public delegate int ForEachCallback(byte[] oid); + public delegate int ForEachCallback(ObjectId oid); private IntPtr nativeBackendPointer; @@ -164,6 +194,11 @@ internal IntPtr GitOdbBackendPointer nativeBackend.Exists = BackendEntryPoints.ExistsCallback; } + if ((supportedOperations & OdbBackendOperations.ExistsPrefix) != 0) + { + nativeBackend.ExistsPrefix = BackendEntryPoints.ExistsPrefixCallback; + } + if ((supportedOperations & OdbBackendOperations.ForEach) != 0) { nativeBackend.Foreach = BackendEntryPoints.ForEachCallback; @@ -191,9 +226,25 @@ private static class BackendEntryPoints public static readonly GitOdbBackend.write_callback WriteCallback = Write; public static readonly GitOdbBackend.writestream_callback WriteStreamCallback = WriteStream; public static readonly GitOdbBackend.exists_callback ExistsCallback = Exists; + public static readonly GitOdbBackend.exists_prefix_callback ExistsPrefixCallback = ExistsPrefix; public static readonly GitOdbBackend.foreach_callback ForEachCallback = Foreach; public static readonly GitOdbBackend.free_callback FreeCallback = Free; + private static OdbBackend MarshalOdbBackend(IntPtr backendPtr) + { + + var intPtr = Marshal.ReadIntPtr(backendPtr, GitOdbBackend.GCHandleOffset); + var odbBackend = GCHandle.FromIntPtr(intPtr).Target as OdbBackend; + + if (odbBackend == null) + { + Proxy.git_error_set_str(GitErrorCategory.Reference, "Cannot retrieve the managed OdbBackend."); + return null; + } + + return odbBackend; + } + private unsafe static int Read( out IntPtr buffer_p, out UIntPtr len_p, @@ -205,50 +256,50 @@ private unsafe static int Read( len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; - - if (odbBackend != null) + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) { - Stream dataStream = null; - GitObjectType objectType; - - try - { - int toReturn = odbBackend.Read(oid.Id, out dataStream, out objectType); - - if (0 == toReturn) - { - // Caller is expected to give us back a stream created with the Allocate() method. - UnmanagedMemoryStream memoryStream = dataStream as UnmanagedMemoryStream; - - if (null == memoryStream) - { - return (int)GitErrorCode.Error; - } + return (int)GitErrorCode.Error; + } - len_p = new UIntPtr((ulong)memoryStream.Capacity); - type_p = objectType; + UnmanagedMemoryStream memoryStream = null; - memoryStream.Seek(0, SeekOrigin.Begin); - buffer_p = new IntPtr(memoryStream.PositionPointer); - } + try + { + ObjectType objectType; + int toReturn = odbBackend.Read(new ObjectId(oid), out memoryStream, out objectType); + if (toReturn != 0) + { return toReturn; } - catch (Exception ex) + + if (memoryStream == null) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; } - finally + + len_p = new UIntPtr((ulong)memoryStream.Capacity); + type_p = objectType.ToGitObjectType(); + + memoryStream.Seek(0, SeekOrigin.Begin); + buffer_p = new IntPtr(memoryStream.PositionPointer); + + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } + finally + { + if (memoryStream != null) { - if (null != dataStream) - { - dataStream.Dispose(); - } + memoryStream.Dispose(); } } - return (int)GitErrorCode.Error; + return (int)GitErrorCode.Ok; } private unsafe static int ReadPrefix( @@ -265,57 +316,54 @@ private unsafe static int ReadPrefix( len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; - - if (odbBackend != null) + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) { - byte[] oid; - Stream dataStream = null; - GitObjectType objectType; - - try - { - // The length of short_oid is described in characters (40 per full ID) vs. bytes (20) - // which is what we care about. - byte[] shortOidArray = new byte[(long)len >> 1]; - Array.Copy(short_oid.Id, shortOidArray, shortOidArray.Length); - - int toReturn = odbBackend.ReadPrefix(shortOidArray, out oid, out dataStream, out objectType); + return (int)GitErrorCode.Error; + } - if (0 == toReturn) - { - // Caller is expected to give us back a stream created with the Allocate() method. - UnmanagedMemoryStream memoryStream = dataStream as UnmanagedMemoryStream; + UnmanagedMemoryStream memoryStream = null; - if (null == memoryStream) - { - return (int)GitErrorCode.Error; - } + try + { + var shortSha = ObjectId.ToString(short_oid.Id, (int)len); - out_oid.Id = oid; - len_p = new UIntPtr((ulong)memoryStream.Capacity); - type_p = objectType; + ObjectId oid; + ObjectType objectType; - memoryStream.Seek(0, SeekOrigin.Begin); - buffer_p = new IntPtr(memoryStream.PositionPointer); - } + int toReturn = odbBackend.ReadPrefix(shortSha, out oid, out memoryStream, out objectType); + if (toReturn != 0) + { return toReturn; } - catch (Exception ex) + + if (memoryStream == null) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; } - finally + + out_oid.Id = oid.RawId; + len_p = new UIntPtr((ulong)memoryStream.Capacity); + type_p = objectType.ToGitObjectType(); + + memoryStream.Seek(0, SeekOrigin.Begin); + buffer_p = new IntPtr(memoryStream.PositionPointer); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } + finally + { + if (memoryStream != null) { - if (null != dataStream) - { - dataStream.Dispose(); - } + memoryStream.Dispose(); } } - return (int)GitErrorCode.Error; + return (int)GitErrorCode.Ok; } private static int ReadHeader( @@ -327,104 +375,97 @@ private static int ReadHeader( len_p = UIntPtr.Zero; type_p = GitObjectType.Bad; - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) + { + return (int)GitErrorCode.Error; + } - if (odbBackend != null) + try { int length; - GitObjectType objectType; + ObjectType objectType; + int toReturn = odbBackend.ReadHeader(new ObjectId(oid), out length, out objectType); - try + if (toReturn != 0) { - int toReturn = odbBackend.ReadHeader(oid.Id, out length, out objectType); - - if (0 == toReturn) - { - len_p = new UIntPtr((uint)length); - type_p = objectType; - } - return toReturn; } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); - } + + len_p = new UIntPtr((uint)length); + type_p = objectType.ToGitObjectType(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + return (int)GitErrorCode.Ok; } private static unsafe int Write( - ref GitOid oid, IntPtr backend, + ref GitOid oid, IntPtr data, UIntPtr len, GitObjectType type) { - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; + long length = ConverToLong(len); - if (odbBackend != null && - len.ToUInt64() < long.MaxValue) + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) { - try - { - using (UnmanagedMemoryStream stream = new UnmanagedMemoryStream((byte*)data, (long)len.ToUInt64())) - { - byte[] finalOid; - - int toReturn = odbBackend.Write(oid.Id, stream, (long)len.ToUInt64(), type, out finalOid); - - if (0 == toReturn) - { - oid.Id = finalOid; - } + return (int)GitErrorCode.Error; + } - return toReturn; - } - } - catch (Exception ex) + try + { + using (var stream = new UnmanagedMemoryStream((byte*)data, length)) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + return odbBackend.Write(new ObjectId(oid), stream, length, type.ToObjectType()); } } - - return (int)GitErrorCode.Error; + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } } private static int WriteStream( out IntPtr stream_out, IntPtr backend, - UIntPtr length, + long len, GitObjectType type) { stream_out = IntPtr.Zero; - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; - - if (odbBackend != null && - length.ToUInt64() < long.MaxValue) + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) { - OdbBackendStream stream; + return (int)GitErrorCode.Error; + } - try - { - int toReturn = odbBackend.WriteStream((long)length.ToUInt64(), type, out stream); + ObjectType objectType = type.ToObjectType(); - if (0 == toReturn) - { - stream_out = stream.GitOdbBackendStreamPointer; - } + try + { + OdbBackendStream stream; + int toReturn = odbBackend.WriteStream(len, objectType, out stream); - return toReturn; - } - catch (Exception ex) + if (toReturn == 0) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + stream_out = stream.GitOdbBackendStreamPointer; } - } - return (int)GitErrorCode.Error; + return toReturn; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } } private static int ReadStream( @@ -434,51 +475,84 @@ private static int ReadStream( { stream_out = IntPtr.Zero; - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) + { + return (int)GitErrorCode.Error; + } - if (odbBackend != null) + try { OdbBackendStream stream; + int toReturn = odbBackend.ReadStream(new ObjectId(oid), out stream); - try + if (toReturn == 0) { - int toReturn = odbBackend.ReadStream(oid.Id, out stream); - - if (0 == toReturn) - { - stream_out = stream.GitOdbBackendStreamPointer; - } - - return toReturn; - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + stream_out = stream.GitOdbBackendStreamPointer; } - } - return (int)GitErrorCode.Error; + return toReturn; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } } private static bool Exists( IntPtr backend, ref GitOid oid) { - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) + { + return false; // Weird + } - if (odbBackend != null) + try { - try - { - return odbBackend.Exists(oid.Id); - } - catch (Exception ex) + return odbBackend.Exists(new ObjectId(oid)); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return false; + } + } + + private static int ExistsPrefix( + ref GitOid found_oid, + IntPtr backend, + ref GitOid short_oid, + UIntPtr len) + { + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) + { + return (int)GitErrorCode.Error; + } + + try + { + ObjectId found; + var shortSha = ObjectId.ToString(short_oid.Id, (int)len); + + found_oid.Id = ObjectId.Zero.RawId; + int result = odbBackend.ExistsPrefix(shortSha, out found); + + if (result == (int)GitErrorCode.Ok) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + found_oid.Id = found.RawId; } - } - return false; + return result; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } } private static int Foreach( @@ -486,39 +560,48 @@ private static int Foreach( GitOdbBackend.foreach_callback_callback cb, IntPtr data) { - OdbBackend odbBackend = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)).Target as OdbBackend; - - if (odbBackend != null) + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) { - try - { - return odbBackend.ForEach(new ForeachState(cb, data).ManagedCallback); - } - catch (Exception ex) - { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); - } + return (int)GitErrorCode.Error; } - return (int)GitErrorCode.Error; + try + { + return odbBackend.ForEach(new ForeachState(cb, data).ManagedCallback); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); + return (int)GitErrorCode.Error; + } } private static void Free( IntPtr backend) { - GCHandle gcHandle = GCHandle.FromIntPtr(Marshal.ReadIntPtr(backend, GitOdbBackend.GCHandleOffset)); - OdbBackend odbBackend = gcHandle.Target as OdbBackend; + OdbBackend odbBackend = MarshalOdbBackend(backend); + if (odbBackend == null) + { + return; + } - if (odbBackend != null) + try { - try - { - odbBackend.Dispose(); - } - catch (Exception ex) + odbBackend.Free(); + + var disposable = odbBackend as IDisposable; + + if (disposable == null) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + return; } + + disposable.Dispose(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } @@ -531,12 +614,14 @@ public ForeachState(GitOdbBackend.foreach_callback_callback cb, IntPtr data) this.ManagedCallback = CallbackMethod; } - private int CallbackMethod(byte[] oid) + private unsafe int CallbackMethod(ObjectId id) { - GitOid gitOid = new GitOid(); - gitOid.Id = oid; + var oid = id.RawId; - return cb(ref gitOid, data); + fixed (void* ptr = &oid[0]) + { + return cb(new IntPtr(ptr), data); + } } public readonly ForEachCallback ManagedCallback; @@ -546,51 +631,95 @@ private int CallbackMethod(byte[] oid) } } + 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)); + } + + return (long)len.ToUInt64(); + } + /// - /// Flags used by subclasses of OdbBackend to indicate which operations they support. + /// Flags used by subclasses of OdbBackend to indicate which operations they support. /// [Flags] protected enum OdbBackendOperations { /// - /// This OdbBackend declares that it supports the Read method. + /// This OdbBackend declares that it supports the Read method. /// Read = 1, /// - /// This OdbBackend declares that it supports the ReadPrefix method. + /// This OdbBackend declares that it supports the ReadPrefix method. /// ReadPrefix = 2, /// - /// This OdbBackend declares that it supports the ReadHeader method. + /// This OdbBackend declares that it supports the ReadHeader method. /// ReadHeader = 4, /// - /// This OdbBackend declares that it supports the Write method. + /// This OdbBackend declares that it supports the Write method. /// Write = 8, /// - /// This OdbBackend declares that it supports the ReadStream method. + /// This OdbBackend declares that it supports the ReadStream method. /// ReadStream = 16, /// - /// This OdbBackend declares that it supports the WriteStream method. + /// This OdbBackend declares that it supports the WriteStream method. /// WriteStream = 32, /// - /// This OdbBackend declares that it supports the Exists method. + /// This OdbBackend declares that it supports the Exists method. /// Exists = 64, /// - /// This OdbBackend declares that it supports the Foreach method. + /// This OdbBackend declares that it supports the ExistsPrefix method. + /// + ExistsPrefix = 128, + + /// + /// This OdbBackend declares that it supports the Foreach method. + /// + ForEach = 256, + } + + /// + /// Libgit2 expected backend return codes. + /// + protected enum ReturnCode + { + /// + /// No error has occured. + /// + GIT_OK = 0, + + /// + /// No object matching the oid, or short oid, can be found in the backend. + /// + GIT_ENOTFOUND = -3, + + /// + /// The given short oid is ambiguous. + /// + GIT_EAMBIGUOUS = -5, + + /// + /// Interruption of the foreach() callback is requested. /// - ForEach = 128, + GIT_EUSER = -7, } } } diff --git a/LibGit2Sharp/OdbBackendStream.cs b/LibGit2Sharp/OdbBackendStream.cs index b666ef72e..e7d177903 100644 --- a/LibGit2Sharp/OdbBackendStream.cs +++ b/LibGit2Sharp/OdbBackendStream.cs @@ -6,8 +6,8 @@ namespace LibGit2Sharp { /// - /// When an OdbBackend implements the WriteStream or ReadStream methods, it returns an OdbBackendStream to libgit2. - /// Libgit2 then uses the OdbBackendStream to read or write from the backend in a streaming fashion. + /// When an OdbBackend implements the WriteStream or ReadStream methods, it returns an OdbBackendStream to libgit2. + /// Libgit2 then uses the OdbBackendStream to read or write from the backend in a streaming fashion. /// public abstract class OdbBackendStream { @@ -30,7 +30,7 @@ protected OdbBackendStream(OdbBackend backend) } /// - /// Invoked by libgit2 when this stream is no longer needed. + /// Invoked by libgit2 when this stream is no longer needed. /// protected virtual void Dispose() { @@ -43,50 +43,36 @@ protected virtual void Dispose() } /// - /// If true, then it is legal to call the Read method. + /// 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. + /// 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. + /// 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. + /// 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 can be retrieved by calling FinalizeWrite. + /// After all bytes have been written to the stream, the object ID is provided to FinalizeWrite. /// - public abstract int FinalizeWrite( - out byte[] oid); + public abstract int FinalizeWrite(ObjectId id); /// - /// The backend object this stream was created by. + /// 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,25 +124,23 @@ 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; - if (odbBackendStream != null && - len.ToUInt64() < long.MaxValue) + if (odbBackendStream != null) { - using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, (long)len.ToUInt64(), FileAccess.ReadWrite)) + long length = OdbBackend.ConverToLong(len); + + using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, length, FileAccess.ReadWrite)) { try { - return odbBackendStream.Read(memoryStream, (long)len.ToUInt64()); + return odbBackendStream.Read(memoryStream, length); } catch (Exception ex) { - Proxy.giterr_set_str(GitErrorCategory.Odb, ex); + Proxy.git_error_set_str(GitErrorCategory.Odb, ex); } } } @@ -164,17 +148,13 @@ 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; - if (odbBackendStream != null && - len.ToUInt64() < long.MaxValue) + if (odbBackendStream != null) { - long length = (long)len.ToUInt64(); + long length = OdbBackend.ConverToLong(len); using (UnmanagedMemoryStream dataStream = new UnmanagedMemoryStream((byte*)buffer, length)) { @@ -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,40 +172,26 @@ private static unsafe int Write( return (int)GitErrorCode.Error; } - private static int FinalizeWrite( - out GitOid oid_p, - IntPtr stream) + private static int FinalizeWrite(IntPtr stream, ref GitOid oid) { - oid_p = default(GitOid); - OdbBackendStream odbBackendStream = GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitOdbBackendStream.GCHandleOffset)).Target as OdbBackendStream; if (odbBackendStream != null) { - byte[] computedObjectId; - try { - int toReturn = odbBackendStream.FinalizeWrite(out computedObjectId); - - if (0 == toReturn) - { - oid_p.Id = computedObjectId; - } - - return toReturn; + return odbBackendStream.FinalizeWrite(new ObjectId(oid)); } 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; @@ -237,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/OrphanedHeadException.cs b/LibGit2Sharp/OrphanedHeadException.cs deleted file mode 100644 index 015dbfc40..000000000 --- a/LibGit2Sharp/OrphanedHeadException.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Runtime.Serialization; - -namespace LibGit2Sharp -{ - /// - /// The exception that is thrown when a operation requiring an existing - /// branch is performed against an unborn branch. - /// - [Serializable] - public class OrphanedHeadException : LibGit2SharpException - { - /// - /// Initializes a new instance of the class. - /// - public OrphanedHeadException() - { - } - - /// - /// Initializes a new instance of the class with a specified error message. - /// - /// A message that describes the error. - public OrphanedHeadException(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 OrphanedHeadException(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 OrphanedHeadException(SerializationInfo info, StreamingContext context) - : base(info, context) - { - } - } -} 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 new file mode 100644 index 000000000..50157eb32 --- /dev/null +++ b/LibGit2Sharp/Patch.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Text; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Holds the patch between two trees. + /// The individual patches for each file can be accessed through the indexer of this class. + /// Building a patch is an expensive operation. If you only need to know which files have been added, + /// deleted, modified, ..., then consider using a simpler . + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class Patch : IEnumerable, IDiffResult + { + private readonly StringBuilder fullPatchBuilder = new StringBuilder(); + + private readonly IDictionary changes = new Dictionary(); + private int linesAdded; + private int linesDeleted; + + /// + /// Needed for mocking purposes. + /// + protected Patch() + { } + + internal unsafe Patch(DiffHandle diff) + { + using (diff) + { + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) + { + 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 unsafe void AddFileChange(git_diff_delta* delta) + { + var treeEntryChanges = new TreeEntryChanges(delta); + + changes.Add(treeEntryChanges.Path, new PatchEntryChanges(delta->flags.HasFlag(GitDiffFlags.GIT_DIFF_FLAG_BINARY), treeEntryChanges)); + } + + 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->new_file.Path != null + ? delta->new_file.Path + : delta->old_file.Path; + var filePath = LaxFilePathMarshaler.FromNative(pathPtr); + + PatchEntryChanges currentChange = this[filePath]; + string prefix = string.Empty; + + switch (line.lineOrigin) + { + case GitDiffLineOrigin.GIT_DIFF_LINE_CONTEXT: + prefix = " "; + break; + + 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; + } + + string formattedOutput = string.Concat(prefix, patchPart); + + fullPatchBuilder.Append(formattedOutput); + currentChange.AppendToPatch(formattedOutput); + + return 0; + } + + #region IEnumerable Members + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return changes.Values.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(); + } + + #endregion + + /// + /// Gets the corresponding to the specified . + /// + public virtual PatchEntryChanges this[string path] + { + get { return this[(FilePath)path]; } + } + + private PatchEntryChanges this[FilePath path] + { + get + { + PatchEntryChanges entryChanges; + if (changes.TryGetValue(path, out entryChanges)) + { + return entryChanges; + } + + return null; + } + } + + /// + /// The total number of lines added in this diff. + /// + public virtual int LinesAdded + { + get { return linesAdded; } + } + + /// + /// The total number of lines deleted in this diff. + /// + public virtual int LinesDeleted + { + get { return linesDeleted; } + } + + /// + /// The full patch file of this diff. + /// + public virtual string Content + { + get { return fullPatchBuilder.ToString(); } + } + + /// + /// Implicit operator for string conversion. + /// + /// . + /// The patch content as string. + public static implicit operator string(Patch patch) + { + return patch.fullPatchBuilder.ToString(); + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "+{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/PatchEntryChanges.cs b/LibGit2Sharp/PatchEntryChanges.cs new file mode 100644 index 000000000..0a7d925b7 --- /dev/null +++ b/LibGit2Sharp/PatchEntryChanges.cs @@ -0,0 +1,78 @@ +namespace LibGit2Sharp +{ + /// + /// Holds the changes between two versions of a file. + /// + public class PatchEntryChanges : ContentChanges + { + private readonly TreeEntryChanges treeEntryChanges; + + /// + /// Needed for mocking purposes. + /// + protected PatchEntryChanges() + { } + + internal PatchEntryChanges(bool isBinaryComparison, TreeEntryChanges treeEntryChanges) + : base(isBinaryComparison) + { + this.treeEntryChanges = treeEntryChanges; + } + + /// + /// The new path. + /// + public virtual string Path + { + get { return treeEntryChanges.Path; } + } + + /// + /// The new . + /// + public virtual Mode Mode + { + get { return treeEntryChanges.Mode; } + } + + /// + /// The new content hash. + /// + public virtual ObjectId Oid + { + get { return treeEntryChanges.Oid; } + } + + /// + /// The kind of change that has been done (added, deleted, modified ...). + /// + public virtual ChangeKind Status + { + get { return treeEntryChanges.Status; } + } + + /// + /// The old path. + /// + public virtual string OldPath + { + get { return treeEntryChanges.OldPath; } + } + + /// + /// The old . + /// + public virtual Mode OldMode + { + get { return treeEntryChanges.OldMode; } + } + + /// + /// The old content hash. + /// + public virtual ObjectId OldOid + { + get { return treeEntryChanges.OldOid; } + } + } +} diff --git a/LibGit2Sharp/PatchStats.cs b/LibGit2Sharp/PatchStats.cs new file mode 100644 index 000000000..3d6bb46cd --- /dev/null +++ b/LibGit2Sharp/PatchStats.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Holds summary information for a diff. + /// The individual patches for each file can be accessed through the indexer of this class. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class PatchStats : IEnumerable, IDiffResult + { + private readonly IDictionary changes = new Dictionary(); + private readonly int totalLinesAdded; + private readonly int totalLinesDeleted; + + /// + /// For mocking. + /// + protected PatchStats() + { } + + internal unsafe PatchStats(DiffHandle diff) + { + using (diff) + { + int count = Proxy.git_diff_num_deltas(diff); + for (int i = 0; i < count; i++) + { + 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; + } + } + } + } + + #region IEnumerable Members + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return changes.Values.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(); + } + + #endregion + + /// + /// Gets the corresponding to the specified . + /// + /// + public virtual ContentChangeStats this[string path] + { + get { return this[(FilePath)path]; } + } + + private ContentChangeStats this[FilePath path] + { + get + { + ContentChangeStats stats; + if (changes.TryGetValue(path, out stats)) + { + return stats; + } + return null; + } + } + + /// + /// The total number of lines added in this diff. + /// + public virtual int TotalLinesAdded + { + get { return totalLinesAdded; } + } + + /// + /// The total number of lines deleted in this diff. + /// + public virtual int TotalLinesDeleted + { + get { return totalLinesDeleted; } + } + + private string DebuggerDisplay + { + get + { + 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 255070ff6..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 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.9.5")] -[assembly: AssemblyFileVersion("0.9.5")] 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 new file mode 100644 index 000000000..764715bb5 --- /dev/null +++ b/LibGit2Sharp/PullOptions.cs @@ -0,0 +1,24 @@ +namespace LibGit2Sharp +{ + /// + /// Parameters controlling Pull behavior. + /// + public sealed class PullOptions + { + /// + /// Constructor. + /// + public PullOptions() + { } + + /// + /// Parameters controlling Fetch behavior. + /// + public FetchOptions FetchOptions { get; set; } + + /// + /// Parameters controlling Merge behavior. + /// + public MergeOptions MergeOptions { get; set; } + } +} diff --git a/LibGit2Sharp/PushOptions.cs b/LibGit2Sharp/PushOptions.cs new file mode 100644 index 000000000..829eb0d60 --- /dev/null +++ b/LibGit2Sharp/PushOptions.cs @@ -0,0 +1,80 @@ +using LibGit2Sharp.Handlers; + +namespace LibGit2Sharp +{ + /// + /// Collection of parameters controlling Push behavior. + /// + 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 + /// the packbuilder when creating that pack file to be sent to the remote. + /// The default is 0, which indicates that the packbuilder will auto-detect + /// the number of threads to create. + /// + public int PackbuilderDegreeOfParallelism { get; set; } + + /// + /// Delegate to report errors when updating references on the remote. + /// + public PushStatusErrorHandler OnPushStatusError { get; set; } + + /// + /// Delegate that progress updates of the network transfer portion of push + /// will be reported through. The frequency of progress updates will not + /// be more than once every 0.5 seconds (in general). + /// + public PushTransferProgressHandler OnPushTransferProgress { get; set; } + + /// + /// Delegate that progress updates of the pack building portion of push + /// will be reported through. The frequency of progress updates will not + /// 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 35dd59ca1..713f13a55 100644 --- a/LibGit2Sharp/PushResult.cs +++ b/LibGit2Sharp/PushResult.cs @@ -3,37 +3,31 @@ namespace LibGit2Sharp { /// - /// Contains the results of a push operation. + /// Contains the results of a push operation. /// public class PushResult { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected PushResult() { } /// - /// s that failed to update. + /// s that failed to update. /// public virtual IEnumerable FailedPushUpdates { - get - { - return failedPushUpdates; - } + get { return failedPushUpdates; } } /// - /// Flag indicating if there were errors reported - /// when updating references on the remote. + /// Flag indicating if there were errors reported + /// when updating references on the remote. /// public virtual bool HasErrors { - get - { - return failedPushUpdates.Count > 0; - } + get { return failedPushUpdates.Count > 0; } } internal PushResult(List failedPushUpdates) diff --git a/LibGit2Sharp/PushStatusError.cs b/LibGit2Sharp/PushStatusError.cs index 980f5a184..6675801a2 100644 --- a/LibGit2Sharp/PushStatusError.cs +++ b/LibGit2Sharp/PushStatusError.cs @@ -1,23 +1,23 @@ namespace LibGit2Sharp { /// - /// Information on an error updating reference on remote during a push. + /// Information on an error updating reference on remote during a push. /// public class PushStatusError { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected PushStatusError() { } /// - /// The reference this status refers to. + /// The reference this status refers to. /// public virtual string Reference { get; private set; } /// - /// The message regarding the update of this reference. + /// The message regarding the update of this reference. /// public virtual string Message { get; private set; } 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 new file mode 100644 index 000000000..4d9e28fbe --- /dev/null +++ b/LibGit2Sharp/RefSpec.cs @@ -0,0 +1,136 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// A push or fetch reference specification + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RefSpec + { + // This is here to keep the pointer alive +#pragma warning disable 0414 + readonly Remote remote; +#pragma warning restore 0414 + readonly IntPtr handle; + + internal unsafe RefSpec(Remote remote, git_refspec* handle) + { + this.remote = remote; + this.handle = new IntPtr(handle); + } + + /// + /// Needed for mocking purposes. + /// + protected RefSpec() + { } + + /// + /// Gets the pattern describing the mapping between remote and local references + /// + 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 + { + return Proxy.git_refspec_direction(this.handle); + } + } + + /// + /// The source reference specifier + /// + public virtual string Source + { + get + { + return Proxy.git_refspec_src(this.handle); + } + } + + /// + /// The target reference specifier + /// + 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 + { + 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); + } + } + } +} diff --git a/LibGit2Sharp/RefSpecCollection.cs b/LibGit2Sharp/RefSpecCollection.cs new file mode 100644 index 000000000..a35710719 --- /dev/null +++ b/LibGit2Sharp/RefSpecCollection.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The collection of s in a + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RefSpecCollection : IEnumerable + { + // 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. + /// + protected RefSpecCollection() + { } + + internal RefSpecCollection(Remote remote, RemoteHandle handle) + { + Ensure.ArgumentNotNull(handle, "handle"); + + this.remote = remote; + this.handle = handle; + + refspecs = new Lazy>(() => RetrieveRefSpecs(remote, handle)); + } + + 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++) + { + refSpecs.Add(new RefSpec(remote, Proxy.git_remote_get_refspec(remoteHandle, i))); + } + + return refSpecs; + } + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return refspecs.Value.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/RefSpecDirection.cs b/LibGit2Sharp/RefSpecDirection.cs new file mode 100644 index 000000000..6c377a040 --- /dev/null +++ b/LibGit2Sharp/RefSpecDirection.cs @@ -0,0 +1,18 @@ +namespace LibGit2Sharp +{ + /// + /// Indicates whether a refspec is a push refspec or a fetch refspec + /// + public enum RefSpecDirection + { + /// + /// Indicates that the refspec is a fetch refspec + /// + Fetch, + + /// + /// Indicates that the refspec is a push refspec + /// + Push + } +} diff --git a/LibGit2Sharp/Reference.cs b/LibGit2Sharp/Reference.cs index 9ba54e939..9a86195d1 100644 --- a/LibGit2Sharp/Reference.cs +++ b/LibGit2Sharp/Reference.cs @@ -7,35 +7,38 @@ namespace LibGit2Sharp { /// - /// A Reference to another git object + /// A Reference to another git object /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class Reference : IEquatable + public abstract class Reference : IEquatable, IBelongToARepository { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(x => x.CanonicalName, x => x.TargetIdentifier); + private readonly IRepository repo; private readonly string canonicalName; private readonly string targetIdentifier; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Reference() { } - /// - /// Initializes a new instance of the class. - /// - /// The canonical name. - /// The target identifier. - protected Reference(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); @@ -45,36 +48,81 @@ internal static T BuildFromPtr(ReferenceSafeHandle handle, Repository repo) w switch (type) { case GitReferenceType.Symbolic: - string targetIdentifier = Proxy.git_reference_target(handle); - - using (ReferenceSafeHandle resolvedHandle = Proxy.git_reference_resolve(handle)) - { - if (resolvedHandle == null) - { - reference = new SymbolicReference(name, targetIdentifier, null); - break; - } + string targetIdentifier = Proxy.git_reference_symbolic_target(handle); - var targetRef = BuildFromPtr(resolvedHandle, repo); - reference = new SymbolicReference(name, targetIdentifier, targetRef); - break; - } + var targetRef = repo.Refs[targetIdentifier]; + reference = new SymbolicReference(repo, name, targetIdentifier, targetRef); + break; case GitReferenceType.Oid: - ObjectId targetOid = Proxy.git_reference_oid(handle); + ObjectId targetOid = Proxy.git_reference_target(handle); reference = new DirectReference(name, repo, targetOid); 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; } /// - /// Gets the full name of this reference. + /// Determines if the proposed reference name is well-formed. + /// + /// + /// - Top-level names must contain only capital letters and underscores, + /// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). + /// + /// - Names prefixed with "refs/" can be almost anything. You must avoid + /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the + /// sequences ".." and "@{" which have special meaning to revparse. + /// + /// The name to be checked. + /// true is the name is valid; false otherwise. + 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. /// public virtual string CanonicalName { @@ -82,17 +130,17 @@ public virtual string CanonicalName } /// - /// Recursively peels the target of the reference until a direct reference is encountered. + /// Recursively peels the target of the reference until a direct reference is encountered. /// - /// The this points to. + /// The this points to. public abstract DirectReference ResolveToDirectReference(); /// - /// Gets the target declared by the reference. - /// - /// If this reference is a , returns the canonical name of the target. - /// Otherwise, if this reference is a , returns the sha of the target. - /// + /// Gets the target declared by the reference. + /// + /// If this reference is a , returns the canonical name of the target. + /// Otherwise, if this reference is a , returns the sha of the target. + /// /// // TODO: Maybe find a better name for this property. public virtual string TargetIdentifier @@ -101,27 +149,27 @@ 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); } /// - /// 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 bool Equals(Reference other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -130,10 +178,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(Reference left, Reference right) { @@ -141,10 +189,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(Reference left, Reference right) { @@ -152,20 +200,55 @@ public override int GetHashCode() } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// - /// The that represents the current . + /// The that represents the current . public override string ToString() { return CanonicalName; } + internal static string LocalBranchPrefix + { + get { return "refs/heads/"; } + } + + internal static string RemoteTrackingBranchPrefix + { + get { return "refs/remotes/"; } + } + + internal static string TagPrefix + { + get { return "refs/tags/"; } + } + + internal static string NotePrefix + { + get { return "refs/notes/"; } + } + private string DebuggerDisplay { get { return string.Format(CultureInfo.InvariantCulture, - "{0} => \"{1}\"", CanonicalName, TargetIdentifier); + "{0} => \"{1}\"", + CanonicalName, + TargetIdentifier); + } + } + + 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 672aec972..92bf85426 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -10,7 +10,7 @@ namespace LibGit2Sharp { /// - /// The Collection of references in a + /// The Collection of references in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class ReferenceCollection : IEnumerable @@ -18,25 +18,25 @@ public class ReferenceCollection : IEnumerable internal readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected ReferenceCollection() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repo. + /// The repo. internal ReferenceCollection(Repository repo) { this.repo = repo; } /// - /// Gets the with the specified name. + /// Gets the with the specified name. /// - /// The canonical name of the reference to resolve. - /// The resolved if it has been found, null otherwise. + /// The canonical name of the reference to resolve. + /// The resolved if it has been found, null otherwise. public virtual Reference this[string name] { get { return Resolve(name); } @@ -45,20 +45,20 @@ public virtual Reference this[string name] #region IEnumerable Members /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { - return Proxy.git_reference_list(repo.Handle, GitReferenceType.ListAll) + return Proxy.git_reference_list(repo.Handle) .Select(n => this[n]) .GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -67,220 +67,788 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Creates a direct reference with the specified name and target + /// Creates a direct or symbolic reference with the specified name and target /// - /// The canonical name of the reference to create. - /// 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) + /// 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. + /// 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, string logMessage, bool allowOverwrite) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(targetId, "targetId"); - using (ReferenceSafeHandle handle = Proxy.git_reference_create_oid(repo.Handle, name, targetId, allowOverwrite)) + using (ReferenceHandle handle = Proxy.git_reference_create(repo.Handle, name, targetId, allowOverwrite, logMessage)) { return (DirectReference)Reference.BuildFromPtr(handle, repo); } } /// - /// Creates a symbolic reference with the specified name and target + /// Creates a direct reference with the specified name and target /// - /// The canonical name of the reference to create. - /// 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) + /// 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 + /// + /// The canonical name of the reference to create. + /// 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) + { + 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); + } + + /// + /// 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 + /// True to allow silent overwriting a potentially existing reference, false otherwise. + /// A new . + 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_create_symbolic(repo.Handle, name, targetRef.CanonicalName, allowOverwrite)) + using (ReferenceHandle handle = Proxy.git_reference_symbolic_create(repo.Handle, + name, + targetRef.CanonicalName, + allowOverwrite, + logMessage)) { return (SymbolicReference)Reference.BuildFromPtr(handle, repo); } } /// - /// Creates a direct or symbolic reference with the specified name and target + /// Creates a symbolic reference with the specified name and target /// - /// The name of the reference to create. - /// The target which can be either a sha or the canonical name of another reference. - /// 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 Add() instead.")] - public virtual Reference Create(string name, string target, bool allowOverwrite = false) + /// The canonical name of the reference to create. + /// The target reference. + /// A new . + public virtual SymbolicReference Add(string name, Reference targetRef) { - return this.Add(name, target, allowOverwrite); + return Add(name, targetRef, null, false); } /// - /// Remove a reference from the repository + /// Creates a symbolic reference with the specified name and target /// - /// The reference to delete. - public virtual void Remove(Reference reference) + /// The canonical name of the reference to create. + /// 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) { - Ensure.ArgumentNotNull(reference, "reference"); + 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"); - using (ReferenceSafeHandle handle = RetrieveReferencePtr(reference.CanonicalName)) + Reference reference = this[name]; + + if (reference == null) { - Proxy.git_reference_delete(handle); + return; } + + Remove(reference); + } + + /// + /// Remove a reference from the repository + /// + /// The reference to delete. + public virtual void Remove(Reference reference) + { + Ensure.ArgumentNotNull(reference, "reference"); + + Proxy.git_reference_remove(repo.Handle, reference.CanonicalName); } /// - /// Delete a reference with the specified name + /// Rename an existing reference with a new name, and update the reflog /// - /// The name of the reference to delete. - [Obsolete("This method will be removed in the next release. Please use Remove() instead.")] - public virtual void Delete(string name) + /// The reference to rename. + /// The new canonical name. + /// Message added to the reflog. + /// A new . + public virtual Reference Rename(Reference reference, string newName, string logMessage) { - this.Remove(name); + return Rename(reference, newName, logMessage, false); } /// - /// Rename an existing reference with a new name + /// Rename an existing reference with a new name, and update the reflog /// - /// The reference to rename. - /// The new canonical name. - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// A new . - public virtual Reference Move(Reference reference, string newName, bool allowOverwrite = false) + /// 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 . + public virtual Reference Rename(Reference reference, string newName, string logMessage, bool allowOverwrite) { Ensure.ArgumentNotNull(reference, "reference"); Ensure.ArgumentNotNullOrEmptyString(newName, "newName"); - using (ReferenceSafeHandle handle = RetrieveReferencePtr(reference.CanonicalName)) + if (logMessage == null) { - Proxy.git_reference_rename(handle, newName, allowOverwrite); + 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 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 . + public virtual Reference Rename(string currentName, string newName, + bool allowOverwrite) + { + return Rename(currentName, newName, null, allowOverwrite); + } + + /// + /// 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 + /// 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(string currentName, string newName, + string logMessage, bool allowOverwrite) + { + Ensure.ArgumentNotNullOrEmptyString(currentName, "currentName"); + + Reference reference = this[currentName]; + + if (reference == null) + { + 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); + } + + /// + /// Rename an existing reference with a new name + /// + /// The reference to rename. + /// 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) + { + 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); } } /// - /// Updates the target of a direct reference. + /// Updates the target of a direct reference. /// - /// The direct reference which target should be updated. - /// The new target. - /// A new . - public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId) + /// The direct reference which target should be updated. + /// The new target. + /// The optional message to log in the of the reference + /// A new . + public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId, string logMessage) { Ensure.ArgumentNotNull(directRef, "directRef"); Ensure.ArgumentNotNull(targetId, "targetId"); - return UpdateTarget(directRef, targetId, - (h, id) => Proxy.git_reference_set_oid(h, id)); + if (directRef.CanonicalName == "HEAD") + { + return UpdateHeadTarget(targetId, 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 symbolic reference. + /// Updates the target of a direct reference. /// - /// The symbolic reference which target should be updated. - /// The new target. - /// A new . - public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef) + /// 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 + /// + /// The direct reference which target should be updated. + /// The new target. + /// A new . + public virtual Reference UpdateTarget(Reference directRef, ObjectId targetId) + { + return UpdateTarget(directRef, targetId, null); + } + + /// + /// Updates the target of a symbolic reference + /// + /// The symbolic reference which target should be updated. + /// The new target. + /// The optional message to log in the of the reference. + /// A new . + public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef, string logMessage) { Ensure.ArgumentNotNull(symbolicRef, "symbolicRef"); Ensure.ArgumentNotNull(targetRef, "targetRef"); - return UpdateTarget(symbolicRef, targetRef, - (h, r) => Proxy.git_reference_set_target(h, r.CanonicalName)); + if (symbolicRef.CanonicalName == "HEAD") + { + return UpdateHeadTarget(targetRef, 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); + } + } + + /// + /// Updates the target of a symbolic reference + /// + /// The symbolic reference which target should be updated. + /// The new target. + /// A new . + public virtual Reference UpdateTarget(Reference symbolicRef, Reference targetRef) + { + return UpdateTarget(symbolicRef, targetRef, null); } - private Reference UpdateTarget(Reference reference, T target, Action setter) + internal Reference MoveHeadTarget(T target) { - if (reference.CanonicalName == "HEAD") + if (target is ObjectId) + { + Proxy.git_repository_set_head_detached(repo.Handle, target as ObjectId); + } + else if (target is DirectReference || target is SymbolicReference) { - if (target is ObjectId) + 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) && targetIdentifier.LooksLikeLocalBranch()) { - return Add("HEAD", target as ObjectId, true); + Proxy.git_repository_set_head(repo.Handle, targetIdentifier); } - - if (target is DirectReference) + else { - return Add("HEAD", target as DirectReference, true); + using (var annotatedCommit = Proxy.git_annotated_commit_from_revspec(repo.Handle, targetIdentifier)) + { + Proxy.git_repository_set_head_detached_from_annotated(repo.Handle, annotatedCommit); + } } - - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, - "'{0}' is not a valid target type.", typeof(T))); } - - using (ReferenceSafeHandle referencePtr = RetrieveReferencePtr(reference.CanonicalName)) + else { - setter(referencePtr, target); - return Reference.BuildFromPtr(referencePtr, repo); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "'{0}' is not a valid target type.", + typeof(T))); } + + return repo.Refs.Head; + } + + 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 ReferenceSafeHandle RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotFound = true) + 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; } /// - /// Returns the list of references of the repository matching the specified . + /// Returns the list of references of the repository matching the specified . /// - /// The glob pattern the reference name should match. + /// The glob pattern the reference name should match. /// A list of references, ready to be enumerated. public virtual IEnumerable FromGlob(string pattern) { Ensure.ArgumentNotNullOrEmptyString(pattern, "pattern"); - return Proxy.git_reference_foreach_glob(repo.Handle, pattern, GitReferenceType.ListAll, Utf8Marshaler.FromNative) - .OrderBy(name => name, StringComparer.Ordinal).Select(n => this[n]); + return Proxy.git_reference_foreach_glob(repo.Handle, pattern, LaxUtf8Marshaler.FromNative) + .Select(n => this[n]); } /// - /// Determines if the proposed reference name is well-formed. + /// Shortcut to return the HEAD reference. /// - /// - /// - Top-level names must contain only capital letters and underscores, - /// and must begin and end with a letter. (e.g. "HEAD", "ORIG_HEAD"). - /// - /// - Names prefixed with "refs/" can be almost anything. You must avoid - /// the characters '~', '^', ':', '\\', '?', '[', and '*', and the - /// sequences ".." and "@{" which have special meaning to revparse. - /// - /// The name to be checked. - /// true is the name is valid; false otherwise. - public virtual bool IsValidName(string canonicalName) + /// + /// A if the HEAD is detached; + /// otherwise a . + /// + public virtual Reference Head { - return Proxy.git_reference_is_valid_name(canonicalName); + get { return this["HEAD"]; } } + /// - /// Shortcut to return the HEAD reference. + /// Find the s among + /// that can reach at least one in the specified . /// - /// - /// A if the HEAD is detached; - /// otherwise a . - /// - public virtual Reference Head + /// 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) { - get { return this["HEAD"]; } + 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()); } } + + /// + /// Returns as a the reflog of the named + /// + /// The canonical name of the reference + /// a , enumerable of + public virtual ReflogCollection Log(string canonicalName) + { + Ensure.ArgumentNotNullOrEmptyString(canonicalName, "canonicalName"); + + return new ReflogCollection(repo, canonicalName); + } + + /// + /// Returns as a the reflog of the + /// + /// The reference + /// a , enumerable of + public virtual ReflogCollection Log(Reference reference) + { + Ensure.ArgumentNotNull(reference, "reference"); + + return new ReflogCollection(repo, reference.CanonicalName); + } + + /// + /// Rewrite some of the commits in the repository and all the references that can reach them. + /// + /// Specifies behavior for this rewrite. + /// The objects to rewrite. + public virtual void RewriteHistory(RewriteHistoryOptions options, params Commit[] commitsToRewrite) + { + Ensure.ArgumentNotNull(commitsToRewrite, "commitsToRewrite"); + + RewriteHistory(options, commitsToRewrite.AsEnumerable()); + } + + /// + /// Rewrite some of the commits in the repository and all the references that can reach them. + /// + /// Specifies behavior for this rewrite. + /// The objects to rewrite. + public virtual void RewriteHistory(RewriteHistoryOptions options, IEnumerable commitsToRewrite) + { + Ensure.ArgumentNotNull(commitsToRewrite, "commitsToRewrite"); + Ensure.ArgumentNotNull(options, "options"); + Ensure.ArgumentNotNullOrEmptyString(options.BackupRefsNamespace, "options.BackupRefsNamespace"); + + IList originalRefs = this.ToList(); + if (originalRefs.Count == 0) + { + // Nothing to do + return; + } + + var historyRewriter = new HistoryRewriter(repo, commitsToRewrite, options); + + historyRewriter.Execute(); + } + + /// + /// Ensure that a reflog exists for the given canonical name + /// + /// Canonical name of the reference + internal void EnsureHasLog(string canonicalName) + { + Proxy.git_reference_ensure_log(repo.Handle, canonicalName); + } } } diff --git a/LibGit2Sharp/ReferenceCollectionExtensions.cs b/LibGit2Sharp/ReferenceCollectionExtensions.cs deleted file mode 100644 index c23c81534..000000000 --- a/LibGit2Sharp/ReferenceCollectionExtensions.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Globalization; -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 (!refsColl.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. - /// True to allow silent overwriting a potentially existing reference, false otherwise. - /// The being worked with. - /// A new . - public static Reference Add(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish, 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 || (refState == RefState.DoesNotExistButLooksValid && gitObject == null)) - { - using (ReferenceSafeHandle handle = Proxy.git_reference_create_symbolic(refsColl.repo.Handle, name, canonicalRefNameOrObjectish, allowOverwrite)) - { - return Reference.BuildFromPtr(handle, refsColl.repo); - } - } - - Ensure.GitObjectIsNotNull(gitObject, canonicalRefNameOrObjectish); - - return refsColl.Add(name, gitObject.Id, allowOverwrite); - } - /// - /// Updates the target of a direct reference. - /// - /// The direct reference which target should be updated. - /// The revparse spec of the target. - /// The being worked with. - public static Reference UpdateTarget(this ReferenceCollection refsColl, Reference directRef, string objectish) - { - Ensure.ArgumentNotNull(directRef, "directRef"); - Ensure.ArgumentNotNull(objectish, "objectish"); - - GitObject target = refsColl.repo.Lookup(objectish); - - Ensure.GitObjectIsNotNull(target, objectish); - - return refsColl.UpdateTarget(directRef, target.Id); - } - - /// - /// 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. - /// The being worked with. - /// A new . - public static Reference Move(this ReferenceCollection refsColl, string currentName, string newName, 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.Move(reference, newName, allowOverwrite); - } - - /// - /// 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 being worked with. - /// A new . - public static Reference UpdateTarget(this ReferenceCollection refsColl, string name, string canonicalRefNameOrObjectish) - { - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - Ensure.ArgumentNotNullOrEmptyString(canonicalRefNameOrObjectish, "canonicalRefNameOrObjectish"); - - if (name == "HEAD") - { - return refsColl.Add("HEAD", canonicalRefNameOrObjectish, true); - } - - Reference reference = refsColl[name]; - - var directReference = reference as DirectReference; - if (directReference != null) - { - return refsColl.UpdateTarget(directReference, canonicalRefNameOrObjectish); - } - - 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); - } - - throw new LibGit2SharpException(string.Format(CultureInfo.InvariantCulture, "Reference '{0}' has an unexpected type ('{1}').", name, reference.GetType())); - } - - /// - /// 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); - } - } -} diff --git a/LibGit2Sharp/ReferenceExtensions.cs b/LibGit2Sharp/ReferenceExtensions.cs new file mode 100644 index 000000000..c29f6f076 --- /dev/null +++ b/LibGit2Sharp/ReferenceExtensions.cs @@ -0,0 +1,35 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Provides helpers to a . + /// + internal static class ReferenceExtensions + { + internal static bool LooksLikeLocalBranch(this string canonicalName) + { + return canonicalName.IsPrefixedBy(Reference.LocalBranchPrefix); + } + + internal static bool LooksLikeRemoteTrackingBranch(this string canonicalName) + { + return canonicalName.IsPrefixedBy(Reference.RemoteTrackingBranchPrefix); + } + + internal static bool LooksLikeTag(this string canonicalName) + { + return canonicalName.IsPrefixedBy(Reference.TagPrefix); + } + + internal static bool LooksLikeNote(this string canonicalName) + { + return canonicalName.IsPrefixedBy(Reference.NotePrefix); + } + + private static bool IsPrefixedBy(this string input, string prefix) + { + return input.StartsWith(prefix, StringComparison.Ordinal); + } + } +} diff --git a/LibGit2Sharp/ReferenceWrapper.cs b/LibGit2Sharp/ReferenceWrapper.cs index 25a9eda88..7fb8497c6 100644 --- a/LibGit2Sharp/ReferenceWrapper.cs +++ b/LibGit2Sharp/ReferenceWrapper.cs @@ -1,31 +1,36 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; namespace LibGit2Sharp { /// - /// A base class for things that wrap a (branch, tag, etc). + /// A base class for things that wrap a (branch, tag, etc). /// /// The type of the referenced Git object. [DebuggerDisplay("{DebuggerDisplay,nq}")] - public abstract class ReferenceWrapper : IEquatable> where TObject : GitObject +#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. + /// 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; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected ReferenceWrapper() { } @@ -41,11 +46,12 @@ protected internal ReferenceWrapper(Repository repo, Reference reference, Func(() => RetrieveTargetObject(reference)); } /// - /// Gets the full name of this reference. + /// Gets the full name of this reference. /// public virtual string CanonicalName { @@ -53,24 +59,35 @@ 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(); } } /// - /// Returns the , a representation of the current reference. + /// The underlying /// - /// The that represents the current reference. + public virtual Reference Reference + { + get + { + return reference; + } + } + + /// + /// Returns the , a representation of the current reference. + /// + /// The that represents the current reference. public override string ToString() { return CanonicalName; } /// - /// Gets the this points to. + /// Gets the this points to. /// protected TObject TargetObject { @@ -78,8 +95,8 @@ protected TObject TargetObject } /// - /// Removes redundent leading namespaces (regarding the kind of - /// reference being wrapped) from the canonical name. + /// Removes redundent leading namespaces (regarding the kind of + /// reference being wrapped) from the canonical name. /// /// The friendly shortened name protected abstract string Shorten(); @@ -102,27 +119,27 @@ private TObject RetrieveTargetObject(Reference reference) } /// - /// 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 bool Equals(ReferenceWrapper other) { return equalityHelper.Equals(this, 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); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -131,10 +148,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(ReferenceWrapper left, ReferenceWrapper right) { @@ -142,10 +159,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(ReferenceWrapper left, ReferenceWrapper right) { @@ -157,9 +174,13 @@ 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) + : "?"); } } + + IRepository IBelongToARepository.Repository { get { return repo; } } } } diff --git a/LibGit2Sharp/ReflogCollection.cs b/LibGit2Sharp/ReflogCollection.cs new file mode 100644 index 000000000..20b1a8b73 --- /dev/null +++ b/LibGit2Sharp/ReflogCollection.cs @@ -0,0 +1,93 @@ +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The is the reflog of a given , as a enumerable of . + /// Reflog is a mechanism to record when the tip of a is updated. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class ReflogCollection : IEnumerable + { + internal readonly Repository repo; + + private readonly string canonicalName; + + /// + /// Needed for mocking purposes. + /// + protected ReflogCollection() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The repo. + /// the canonical name of the to retrieve reflog entries on. + internal ReflogCollection(Repository repo, string canonicalName) + { + Ensure.ArgumentNotNullOrEmptyString(canonicalName, "canonicalName"); + Ensure.ArgumentNotNull(repo, "repo"); + + if (!Reference.IsValidName(canonicalName)) + { + throw new InvalidSpecificationException("The given reference name '{0}' is not valid", canonicalName); + } + + this.repo = repo; + this.canonicalName = canonicalName; + } + + #region Implementation of IEnumerable + + /// + /// Returns an enumerator that iterates through the collection. + /// + /// The enumerator returns the by descending order (last reflog entry is returned first). + /// + /// + /// An object that can be used to iterate through the collection. + public virtual unsafe IEnumerator GetEnumerator() + { + var entries = new List(); + + using (ReflogHandle reflog = Proxy.git_reflog_read(repo.Handle, canonicalName)) + { + var entriesCount = Proxy.git_reflog_entrycount(reflog); + + for (int i = 0; i < entriesCount; i++) + { + git_reflog_entry* handle = Proxy.git_reflog_entry_byindex(reflog, i); + entries.Add(new ReflogEntry(handle)); + } + } + + return entries.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(); + } + + #endregion + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); + } + } + } +} diff --git a/LibGit2Sharp/ReflogEntry.cs b/LibGit2Sharp/ReflogEntry.cs new file mode 100644 index 000000000..d5f064c5a --- /dev/null +++ b/LibGit2Sharp/ReflogEntry.cs @@ -0,0 +1,68 @@ +using System; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// As single entry of a + /// a describes one single update on a particular reference + /// + public class ReflogEntry + { + private readonly ObjectId _from; + private readonly ObjectId _to; + private readonly Signature _committer; + private readonly string message; + + /// + /// Needed for mocking purposes. + /// + protected ReflogEntry() + { } + + /// + /// Initializes a new instance of the class. + /// + /// a to the reflog entry + internal unsafe ReflogEntry(git_reflog_entry* entryHandle) + { + _from = Proxy.git_reflog_entry_id_old(entryHandle); + _to = Proxy.git_reflog_entry_id_new(entryHandle); + _committer = Proxy.git_reflog_entry_committer(entryHandle); + message = Proxy.git_reflog_entry_message(entryHandle); + } + + /// + /// targeted before the reference update described by this + /// + public virtual ObjectId From + { + get { return _from; } + } + + /// + /// targeted after the reference update described by this + /// + public virtual ObjectId To + { + get { return _to; } + } + + /// + /// of the committer of this reference update + /// + public virtual Signature Committer + { + get { return _committer; } + } + + /// + /// the message assiocated to this reference update + /// + public virtual string Message + { + get { return message; } + } + } +} diff --git a/LibGit2Sharp/Remote.cs b/LibGit2Sharp/Remote.cs index d46cd82f5..401a7ddd0 100644 --- a/LibGit2Sharp/Remote.cs +++ b/LibGit2Sharp/Remote.cs @@ -1,182 +1,188 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// A remote repository whose branches are tracked. + /// A remote repository whose branches are tracked. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Remote : IEquatable + public class Remote : IBelongToARepository, IDisposable { - private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.Name, x => x.Url); + internal readonly Repository repository; - private readonly Repository repository; + private readonly RefSpecCollection refSpecs; + + readonly RemoteHandle handle; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Remote() { } - private Remote(Repository repository, string name, string url) + internal Remote(RemoteHandle handle, Repository repository) { this.repository = repository; - this.Name = name; - this.Url = url; + this.handle = handle; + refSpecs = new RefSpecCollection(this, handle); + repository.RegisterForCleanup(this); } - internal static Remote BuildFromPtr(RemoteSafeHandle handle, Repository repo) + /// + /// The finalizer for the class. + /// + ~Remote() { - string name = Proxy.git_remote_name(handle); - string url = Proxy.git_remote_url(handle); - - var remote = new Remote(repo, name, url); - - return remote; + Dispose(false); } - /// - /// Gets the alias of this remote repository. - /// - public virtual string Name { get; private set; } + #region IDisposable - /// - /// Gets the url to use to communicate with this remote repository. - /// - public virtual string Url { get; private set; } + bool disposedValue = false; // To detect redundant calls /// - /// Fetch from the . + /// Release the unmanaged remote object /// - /// Optional parameter indicating what tags to download. - /// Progress callback. Corresponds to libgit2 progress callback. - /// Completion callback. Corresponds to libgit2 completion callback. - /// UpdateTips callback. Corresponds to libgit2 update_tips callback. - /// Callback method that transfer progress will be reported through. - /// Reports the client's state regarding the received and processed (bytes, objects) from the server. - /// Credentials to use for username/password authentication. - public virtual void Fetch( - TagFetchMode tagFetchMode = TagFetchMode.Auto, - ProgressHandler onProgress = null, - CompletionHandler onCompletion = null, - UpdateTipsHandler onUpdateTips = null, - TransferProgressHandler onTransferProgress = null, - Credentials credentials = null) + public void Dispose() { - // We need to keep a reference to the git_cred_acquire_cb callback around - // so it will not be garbage collected before we are done with it. - // Note that we also have a GC.KeepAlive call at the end of the method. - NativeMethods.git_cred_acquire_cb credentialCallback = null; + Dispose(true); + GC.SuppressFinalize(this); + } - using (RemoteSafeHandle remoteHandle = Proxy.git_remote_load(repository.Handle, this.Name, true)) + void Dispose(bool disposing) + { + if (!disposedValue) { - var callbacks = new RemoteCallbacks(onProgress, onCompletion, onUpdateTips); - GitRemoteCallbacks gitCallbacks = callbacks.GenerateCallbacks(); - - Proxy.git_remote_set_autotag(remoteHandle, tagFetchMode); - - if (credentials != null) + if (handle != null) { - credentialCallback = (out IntPtr cred, IntPtr url, IntPtr username_from_url, uint types, IntPtr payload) => - NativeMethods.git_cred_userpass_plaintext_new(out cred, credentials.Username, credentials.Password); - - Proxy.git_remote_set_cred_acquire_cb( - remoteHandle, - credentialCallback, - IntPtr.Zero); + handle.Dispose(); } - // 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); - - try - { - Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch); - Proxy.git_remote_download(remoteHandle, onTransferProgress); - Proxy.git_remote_update_tips(remoteHandle); - } - finally - { - Proxy.git_remote_disconnect(remoteHandle); - } + disposedValue = true; } + } + + #endregion + + /// + /// Gets the alias of this remote repository. + /// + 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 { return Proxy.git_remote_url(handle); } + } - // To be safe, make sure the credential callback is kept until - // alive until at least this point. - GC.KeepAlive(credentialCallback); + /// + /// 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; } } /// - /// Determines whether the specified is equal to the current . + /// Gets the Tag Fetch Mode of the remote - indicating how tags are fetched. + /// + public virtual TagFetchMode TagFetchMode + { + get { return Proxy.git_remote_autotag(handle); } + } + + /// + /// Gets the list of s defined for this + /// + public virtual IEnumerable RefSpecs { get { return refSpecs; } } + + /// + /// Gets the list of s defined for this + /// that are intended to be used during a Fetch operation /// - /// 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 IEnumerable FetchRefSpecs { - return Equals(obj as Remote); + get { return refSpecs.Where(r => r.Direction == RefSpecDirection.Fetch); } } /// - /// Determines whether the specified is equal to the current . + /// Gets the list of s defined for this + /// that are intended to be used during a Push operation /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. - public bool Equals(Remote other) + public virtual IEnumerable PushRefSpecs { - return equalityHelper.Equals(this, other); + get { return refSpecs.Where(r => r.Direction == RefSpecDirection.Push); } } /// - /// Returns the hash code for this instance. + /// Transform a reference to its source reference using the 's default fetchspec. /// - /// A 32-bit signed integer hash code. - public override int GetHashCode() + /// The reference to transform. + /// The transformed reference. + internal unsafe string FetchSpecTransformToSource(string reference) { - return equalityHelper.GetHashCode(this); + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repository.Handle, Name, true)) + { + git_refspec* fetchSpecPtr = Proxy.git_remote_get_refspec(remoteHandle, 0); + return Proxy.git_refspec_rtransform(new IntPtr(fetchSpecPtr), reference); + } } /// - /// Tests if two are equal. + /// Determines if the proposed remote name is well-formed. /// - /// First to compare. - /// Second to compare. - /// True if the two objects are equal; false otherwise. - public static bool operator ==(Remote left, Remote right) + /// The name to be checked. + /// true if the name is valid; false otherwise. + public static bool IsValidName(string name) { - return Equals(left, right); + return Proxy.git_remote_is_valid_name(name); } /// - /// Tests if two are different. + /// 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. + /// /// - /// First to compare. - /// Second to compare. - /// True if the two objects are different; false otherwise. - public static bool operator !=(Remote left, Remote right) + public virtual bool AutomaticallyPruneOnFetch { - return !Equals(left, right); + get + { + var remotePrune = repository.Config.Get("remote", Name, "prune"); + + if (remotePrune != null) + { + return remotePrune.Value; + } + + var fetchPrune = repository.Config.Get("fetch.prune"); + + 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); } } + + IRepository IBelongToARepository.Repository { get { return repository; } } } } diff --git a/LibGit2Sharp/RemoteCallbacks.cs b/LibGit2Sharp/RemoteCallbacks.cs index 3cc40cfae..6061b10e1 100644 --- a/LibGit2Sharp/RemoteCallbacks.cs +++ b/LibGit2Sharp/RemoteCallbacks.cs @@ -5,42 +5,101 @@ namespace LibGit2Sharp { /// - /// Class to translate libgit2 callbacks into delegates exposed by LibGit2Sharp. - /// Handles generating libgit2 git_remote_callbacks datastructure given a set - /// of LibGit2Sharp delegates and handles propagating libgit2 callbacks into - /// corresponding LibGit2Sharp exposed delegates. + /// Class to translate libgit2 callbacks into delegates exposed by LibGit2Sharp. + /// Handles generating libgit2 git_remote_callbacks datastructure given a set + /// of LibGit2Sharp delegates and handles propagating libgit2 callbacks into + /// corresponding LibGit2Sharp exposed delegates. /// internal class RemoteCallbacks { - internal RemoteCallbacks(ProgressHandler onProgress = null, CompletionHandler onCompletion = null, UpdateTipsHandler onUpdateTips = null) + internal RemoteCallbacks(CredentialsHandler credentialsProvider) { - Progress = onProgress; - Completion = onCompletion; - UpdateTips = onUpdateTips; + CredentialsProvider = credentialsProvider; + } + + internal RemoteCallbacks(PushOptions pushOptions) + { + if (pushOptions == null) + { + return; + } + + PushTransferProgress = pushOptions.OnPushTransferProgress; + PackBuilderProgress = pushOptions.OnPackBuilderProgress; + CredentialsProvider = pushOptions.CredentialsProvider; + CertificateCheck = pushOptions.CertificateCheck; + PushStatusError = pushOptions.OnPushStatusError; + PrePushCallback = pushOptions.OnNegotiationCompletedBeforePush; + } + + internal RemoteCallbacks(FetchOptionsBase fetchOptions) + { + if (fetchOptions == null) + { + return; + } + + Progress = fetchOptions.OnProgress; + DownloadTransferProgress = fetchOptions.OnTransferProgress; + UpdateTips = fetchOptions.OnUpdateTips; + CredentialsProvider = fetchOptions.CredentialsProvider; + CertificateCheck = fetchOptions.CertificateCheck; } #region Delegates /// - /// Progress callback. Corresponds to libgit2 progress callback. + /// Progress callback. Corresponds to libgit2 progress callback. /// private readonly ProgressHandler Progress; /// - /// UpdateTips callback. Corresponds to libgit2 update_tips callback. + /// UpdateTips callback. Corresponds to libgit2 update_tips callback. /// private readonly UpdateTipsHandler UpdateTips; /// - /// Completion callback. Corresponds to libgit2 Completion callback. + /// 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 CompletionHandler Completion; + 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 - internal GitRemoteCallbacks GenerateCallbacks() + /// + /// The credentials to use for authentication. + /// + private readonly CredentialsHandler CredentialsProvider; + + /// + /// Callback to perform validation on the certificate + /// + private readonly CertificateCheckHandler CertificateCheck; + + internal unsafe GitRemoteCallbacks GenerateCallbacks() { - GitRemoteCallbacks callbacks = new GitRemoteCallbacks {version = 1}; + var callbacks = new GitRemoteCallbacks { version = 1 }; if (Progress != null) { @@ -52,9 +111,39 @@ internal GitRemoteCallbacks GenerateCallbacks() callbacks.update_tips = GitUpdateTipsHandler; } - if (Completion != null) + 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.completion = GitCompletionHandler; + callbacks.pack_progress = GitPackbuilderProgressHandler; + } + + if (PrePushCallback != null) + { + callbacks.push_negotiation = GitPushNegotiationHandler; } return callbacks; @@ -63,67 +152,224 @@ internal GitRemoteCallbacks GenerateCallbacks() #region Handlers to respond to callbacks raised by libgit2 /// - /// Handler for libgit2 Progress callback. Converts values - /// received from libgit2 callback to more suitable types - /// and calls delegate provided by LibGit2Sharp consumer. + /// Handler for libgit2 Progress callback. Converts values + /// received from libgit2 callback to more suitable types + /// and calls delegate provided by LibGit2Sharp consumer. /// /// IntPtr to string from libgit2 /// length of string - /// - private void GitProgressHandler(IntPtr str, int len, IntPtr data) + /// IntPtr to optional payload passed back to the callback. + /// 0 on success; a negative value to abort the process. + private int GitProgressHandler(IntPtr str, int len, IntPtr data) { ProgressHandler onProgress = Progress; + bool shouldContinue = true; + if (onProgress != null) { - string message = Utf8Marshaler.FromNative(str, len); - onProgress(message); + string message = LaxUtf8Marshaler.FromNative(str, len); + shouldContinue = onProgress(message); } + + return Proxy.ConvertResultToCancelFlag(shouldContinue); } /// - /// Handler for libgit2 update_tips callback. Converts values - /// received from libgit2 callback to more suitable types - /// and calls delegate provided by LibGit2Sharp consumer. + /// Handler for libgit2 update_tips callback. Converts values + /// received from libgit2 callback to more suitable types + /// and calls delegate provided by LibGit2Sharp consumer. /// /// IntPtr to string /// Old reference ID /// New referene ID - /// - /// + /// IntPtr to optional payload passed back to the callback. + /// 0 on success; a negative value to abort the process. private int GitUpdateTipsHandler(IntPtr str, ref GitOid oldId, ref GitOid newId, IntPtr data) { UpdateTipsHandler onUpdateTips = UpdateTips; - int result = 0; + bool shouldContinue = true; if (onUpdateTips != null) { - string refName = Utf8Marshaler.FromNative(str); - result = onUpdateTips(refName, oldId, newId); + string refName = LaxUtf8Marshaler.FromNative(str); + shouldContinue = onUpdateTips(refName, oldId, 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 result; + return Proxy.ConvertResultToCancelFlag(true); } /// - /// Handler for libgit2 completion callback. Converts values - /// received from libgit2 callback to more suitable types - /// and calls delegate provided by LibGit2Sharp consumer. + /// The delegate with the signature that matches the native git_transfer_progress_callback function's signature. /// - /// - /// - /// - private int GitCompletionHandler(RemoteCompletionType remoteCompletionType, IntPtr data) + /// structure containing progress information. + /// Payload data. + /// the result of the wrapped + private int GitDownloadTransferProgressHandler(ref GitTransferProgress progress, IntPtr payload) { - CompletionHandler completion = Completion; - int result = 0; + bool shouldContinue = true; + + if (DownloadTransferProgress != null) + { + shouldContinue = DownloadTransferProgress(new TransferProgress(progress)); + } + + return Proxy.ConvertResultToCancelFlag(shouldContinue); + } - if (completion != null) + 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); + + 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); + } + + 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) { - result = completion(remoteCompletionType); + Log.Write(LogLevel.Error, exception.ToString()); + Proxy.git_error_set_str(GitErrorCategory.Callback, exception); + result = false; } - return result; + return Proxy.ConvertResultToCancelFlag(result); } #endregion diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index 94da5eb62..45e71c8b2 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -6,11 +6,12 @@ using System.Linq; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; +using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// The collection of in a + /// The collection of in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RemoteCollection : IEnumerable @@ -18,7 +19,7 @@ public class RemoteCollection : IEnumerable private readonly Repository repository; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected RemoteCollection() { } @@ -29,10 +30,10 @@ internal RemoteCollection(Repository repository) } /// - /// Gets the with the specified name. + /// Gets the with the specified name. /// - /// The name of the remote to retrieve. - /// The retrived if it has been found, null otherwise. + /// The name of the remote to retrieve. + /// The retrived if it has been found, null otherwise. public virtual Remote this[string name] { get { return RemoteForName(name, false); } @@ -42,16 +43,34 @@ internal Remote RemoteForName(string name, bool shouldThrowIfNotFound = true) { Ensure.ArgumentNotNull(name, "name"); - using (RemoteSafeHandle handle = Proxy.git_remote_load(repository.Handle, name, shouldThrowIfNotFound)) + 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 name of the remote to update. + /// Delegate to perform updates on the remote. + public virtual void Update(string remote, params Action[] actions) + { + var updater = new RemoteUpdater(repository, remote); + + repository.Config.WithinTransaction(() => { - return handle == null ? null : Remote.BuildFromPtr(handle, this.repository); - } + foreach (Action action in actions) + { + action(updater); + } + }); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return Proxy @@ -61,99 +80,93 @@ public virtual IEnumerator GetEnumerator() } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// - /// Creates a with the specified name and for the repository at the specified location. - /// - /// A default fetch refspec will be added for this remote. - /// + /// Creates a with the specified name and for the repository at the specified location. + /// + /// A default fetch refspec will be added for this remote. + /// /// - /// The name of the remote to create. - /// The location of the repository. - /// A new . + /// The name of the remote to create. + /// The location of the repository. + /// A new . 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); } /// - /// Creates a with the specified name and for the repository at the specified location. - /// - /// A default fetch refspec will be added for this remote. - /// + /// Creates a with the specified name and for the repository at the specified location. /// - /// The name of the remote to create. - /// The location of the repository. - /// A new . - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Remote Create(string name, string url) + /// The name of the remote to create. + /// The location of the repository. + /// The refSpec to be used when fetching from this remote. + /// A new . + public virtual Remote Add(string name, string url, string fetchRefSpec) { - return Add(name, url); + Ensure.ArgumentNotNull(name, "name"); + Ensure.ArgumentNotNull(url, "url"); + Ensure.ArgumentNotNull(fetchRefSpec, "fetchRefSpec"); + + RemoteHandle handle = Proxy.git_remote_create_with_fetchspec(repository.Handle, name, url, fetchRefSpec); + return new Remote(handle, this.repository); } /// - /// Creates a with the specified name and for the repository at the specified location. + /// Deletes the with the specified name. /// - /// The name of the remote to create. - /// The location of the repository. - /// The refSpec to be used when fetching from this remote. - /// A new . - public virtual Remote Add(string name, string url, string fetchRefSpec) + /// The name of the remote to remove. + /// A new . + public virtual void Remove(string name) { Ensure.ArgumentNotNull(name, "name"); - Ensure.ArgumentNotNull(url, "url"); - Ensure.ArgumentNotNull(fetchRefSpec, "fetchRefSpec"); - using (RemoteSafeHandle handle = Proxy.git_remote_create(repository.Handle, name, url)) - { - Proxy.git_remote_set_fetchspec(handle, fetchRefSpec); - Proxy.git_remote_save(handle); - return Remote.BuildFromPtr(handle, this.repository); - } + Proxy.git_remote_delete(repository.Handle, name); } /// - /// Creates a with the specified name and for the repository at the specified location. + /// Renames an existing . /// - /// The name of the remote to create. - /// The location of the repository. - /// The refSpec to be used when fetching from this remote.. - /// A new . - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Remote Create(string name, string url, string fetchRefSpec) + /// The current remote name. + /// The new name the existing remote should bear. + /// A new . + public virtual Remote Rename(string name, string newName) { - return Add(name, url); + return Rename(name, newName, null); } /// - /// Determines if the proposed remote name is well-formed. + /// Renames an existing . /// - /// The name to be checked. - /// true is the name is valid; false otherwise. - public virtual bool IsValidName(string name) + /// The current remote 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) { - return Proxy.git_remote_is_valid_name(name); + Ensure.ArgumentNotNull(name, "name"); + Ensure.ArgumentNotNull(newName, "newName"); + + Proxy.git_remote_rename(repository.Handle, name, newName, callback); + return this[newName]; } 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/RemoteCompletionType.cs b/LibGit2Sharp/RemoteCompletionType.cs index 8e48e161e..9fb6c4406 100644 --- a/LibGit2Sharp/RemoteCompletionType.cs +++ b/LibGit2Sharp/RemoteCompletionType.cs @@ -1,22 +1,22 @@ namespace LibGit2Sharp { /// - /// git_remote_completion types. + /// git_remote_completion types. /// public enum RemoteCompletionType { /// - /// Download. + /// Download. /// Download = 0, /* GIT_REMOTE_COMPLETION_DOWNLOAD */ /// - /// Indexing. + /// Indexing. /// Indexing, /* GIT_REMOTE_COMPLETION_INDEXING */ /// - /// Error. + /// Error. /// Error, /* GIT_REMOTE_COMPLETION_ERROR */ } 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 new file mode 100644 index 000000000..53fd33a4b --- /dev/null +++ b/LibGit2Sharp/RemoteUpdater.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// Exposes properties of a remote that can be updated. + /// + public class RemoteUpdater + { + private readonly UpdatingCollection fetchRefSpecs; + private readonly UpdatingCollection pushRefSpecs; + private readonly Repository repo; + private readonly string remoteName; + + /// + /// Needed for mocking purposes. + /// + protected RemoteUpdater() + { } + + 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); + } + + 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() + { + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) + { + return Proxy.git_remote_get_fetch_refspecs(remoteHandle); + } + } + + private void SetFetchRefSpecs(IEnumerable value) + { + 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() + { + using (RemoteHandle remoteHandle = Proxy.git_remote_lookup(repo.Handle, remoteName, true)) + { + return Proxy.git_remote_get_push_refspecs(remoteHandle); + } + } + + private void SetPushRefSpecs(IEnumerable value) + { + 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); + } + } + + /// + /// Set the default TagFetchMode value for the remote. + /// + public virtual TagFetchMode TagFetchMode + { + set { Proxy.git_remote_set_autotag(repo.Handle, remoteName, value); } + } + + /// + /// Sets the url defined for this + /// + public virtual string Url + { + 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); } + } + + /// + /// Sets the list of s defined for this that are intended to + /// be used during a Fetch operation + /// + public virtual ICollection FetchRefSpecs + { + get { return fetchRefSpecs; } + set { fetchRefSpecs.ReplaceAll(value); } + } + + /// + /// Sets or gets the list of s defined for this that are intended to + /// be used during a Push operation + /// + public virtual ICollection PushRefSpecs + { + get { return pushRefSpecs; } + set { pushRefSpecs.ReplaceAll(value); } + } + + private class UpdatingCollection : ICollection + { + private readonly Lazy> list; + private readonly Action> setter; + + public UpdatingCollection(Func> getter, + Action> setter) + { + list = new Lazy>(() => new List(getter())); + this.setter = setter; + } + + public void Add(T item) + { + list.Value.Add(item); + Save(); + } + + public void Clear() + { + list.Value.Clear(); + Save(); + } + + public bool Contains(T item) + { + return list.Value.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + list.Value.CopyTo(array, arrayIndex); + } + + public int Count + { + get { return list.Value.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + public bool Remove(T item) + { + if (!list.Value.Remove(item)) + { + return false; + } + + Save(); + return true; + } + + public IEnumerator GetEnumerator() + { + return list.Value.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return list.Value.GetEnumerator(); + } + + public void ReplaceAll(IEnumerable newValues) + { + Ensure.ArgumentNotNull(newValues, "newValues"); + list.Value.Clear(); + list.Value.AddRange(newValues); + Save(); + } + + private void Save() + { + setter(list.Value); + } + } + } +} diff --git a/LibGit2Sharp/RemoveFromIndexException.cs b/LibGit2Sharp/RemoveFromIndexException.cs new file mode 100644 index 000000000..847e4026e --- /dev/null +++ b/LibGit2Sharp/RemoveFromIndexException.cs @@ -0,0 +1,60 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#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. + /// + /// 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) + { + } + + /// + /// 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 RemoveFromIndexException(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 RemoveFromIndexException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/RenameDetails.cs b/LibGit2Sharp/RenameDetails.cs new file mode 100644 index 000000000..8742ff0d3 --- /dev/null +++ b/LibGit2Sharp/RenameDetails.cs @@ -0,0 +1,121 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Holds the rename details of a particular file. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RenameDetails : IEquatable + { + private readonly string oldFilePath; + private readonly string newFilePath; + private readonly int similarity; + + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.OldFilePath, x => x.NewFilePath, x => x.Similarity); + + /// + /// Needed for mocking purposes. + /// + protected RenameDetails() + { } + + internal RenameDetails(string oldFilePath, string newFilePath, int similarity) + { + this.oldFilePath = oldFilePath; + this.newFilePath = newFilePath; + this.similarity = similarity; + } + + /// + /// Gets the relative filepath to the working directory of the old file (the rename source). + /// + public virtual string OldFilePath + { + get { return oldFilePath; } + } + + /// + /// Gets the relative filepath to the working directory of the new file (the rename target). + /// + public virtual string NewFilePath + { + get { return newFilePath; } + } + + /// + /// Gets the similarity between the old file an the new file (0-100). + /// + public virtual int Similarity + { + get { return similarity; } + } + + /// + /// 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 RenameDetails); + } + + /// + /// 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(RenameDetails 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 ==(RenameDetails left, RenameDetails 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 !=(RenameDetails left, RenameDetails right) + { + return !Equals(left, right); + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, + "{0} -> {1} [{2}%]", + OldFilePath, + NewFilePath, + Similarity); + } + } + } +} diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 1eacf6875..9ac5e2424 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -4,28 +4,25 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Reflection; using System.Text.RegularExpressions; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// A Repository is the primary interface into a git repository + /// A Repository is the primary interface into a git repository /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class Repository : IRepository + public sealed class Repository : IRepository { private readonly bool isBare; private readonly BranchCollection branches; private readonly CommitLog commits; private readonly Lazy config; - private readonly RepositorySafeHandle handle; - private readonly Index index; - private readonly ConflictCollection conflicts; + private readonly RepositoryHandle handle; + private readonly Lazy index; private readonly ReferenceCollection refs; private readonly TagCollection tags; private readonly StashCollection stashes; @@ -34,32 +31,144 @@ public 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 static readonly Lazy versionRetriever = new Lazy(RetrieveVersion); + 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 + /// 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, providing ooptional behavioral overrides through parameter. - /// 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. + /// 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. + /// + /// 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. + /// 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_from_worktree(worktreeHandle); + RegisterForCleanup(handle); + RegisterForCleanup(worktreeHandle); + + isBare = Proxy.git_repository_is_bare(handle); + + Func indexBuilder = () => new Index(this); + + string configurationGlobalFilePath = 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 = Proxy.git_repository_open(path); + 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; @@ -73,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) { @@ -89,15 +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(); - conflicts = new ConflictCollection(this); + index = new Lazy(() => indexBuilder()); } commits = new CommitLog(this); @@ -106,18 +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 { @@ -126,38 +241,60 @@ public Repository(string path, RepositoryOptions options = null) } } - private void EagerlyLoadTheConfigIfAnyPathHaveBeenPassed(RepositoryOptions options) + /// + /// Check if parameter leads to a valid git repository. + /// + /// + /// The path to the git repository to check, 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. + /// + /// True if a repository can be resolved through this path; false otherwise + static public bool IsValid(string path) { - if (options == null) + Ensure.ArgumentNotNull(path, "path"); + + if (string.IsNullOrWhiteSpace(path)) { - return; + return false; } - if (options.GlobalConfigurationLocation == null && - options.XdgConfigurationLocation == null && - options.SystemConfigurationLocation == null) + try { - return; + Proxy.git_repository_open_ext(path, RepositoryOpenFlags.NoSearch, null); + } + catch (RepositoryNotFoundException) + { + return false; } - // Dirty hack to force the eager load of the configuration - // without Resharper pestering about useless code + return true; + } + + private void EagerlyLoadComponentsWithSpecifiedPaths(RepositoryOptions options) + { + if (options == null) + { + return; + } - if (!Config.HasConfig(ConfigurationLevel.Local)) + if (!string.IsNullOrEmpty(options.IndexPath)) { - 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; } } /// - /// Shortcut to return the branch pointed to by HEAD + /// Shortcut to return the branch pointed to by HEAD /// - /// public Branch Head { get @@ -179,7 +316,7 @@ public Branch Head } /// - /// Provides access to the configuration settings for this repository. + /// Provides access to the configuration settings for this repository. /// public Configuration Config { @@ -187,7 +324,7 @@ public Configuration Config } /// - /// Gets the index. + /// Gets the index. /// public Index Index { @@ -198,61 +335,47 @@ public Index Index throw new BareRepositoryException("Index is not available in a bare repository."); } - return index; + return index != null ? index.Value : null; } } /// - /// Gets the conflicts that exist. + /// Manipulate the currently ignored files. /// - public ConflictCollection Conflicts + public Ignore Ignore { - get - { - if (isBare) - { - throw new BareRepositoryException("Conflicts are not available in a bare repository."); - } - - return conflicts; - } + get { return ignore; } } /// - /// Manipulate the currently ignored files. + /// Provides access to network functionality for a repository. /// - public Ignore Ignore + public Network Network { - get - { - return ignore; - } + get { return network.Value; } } /// - /// Provides access to network functionality for a repository. + /// Provides access to rebase functionality for a repository. /// - public Network Network + public Rebase Rebase { get { - return network.Value; + return rebaseOperation.Value; } } /// - /// Gets the database. + /// Gets the database. /// public ObjectDatabase ObjectDatabase { - get - { - return odb.Value; - } + get { return odb.Value; } } /// - /// Lookup and enumerate references in the repository. + /// Lookup and enumerate references in the repository. /// public ReferenceCollection Refs { @@ -260,17 +383,8 @@ public ReferenceCollection Refs } /// - /// Lookup and manage remotes in the repository. - /// - [Obsolete("This property will be removed in the next release. Please use Repository.Network.Remotes instead.")] - public RemoteCollection Remotes - { - get { return Network.Remotes; } - } - - /// - /// Lookup and enumerate commits in the repository. - /// Iterating this collection directly starts walking from the HEAD. + /// Lookup and enumerate commits in the repository. + /// Iterating this collection directly starts walking from the HEAD. /// public IQueryableCommitLog Commits { @@ -278,7 +392,7 @@ public IQueryableCommitLog Commits } /// - /// Lookup and enumerate branches in the repository. + /// Lookup and enumerate branches in the repository. /// public BranchCollection Branches { @@ -286,7 +400,7 @@ public BranchCollection Branches } /// - /// Lookup and enumerate tags in the repository. + /// Lookup and enumerate tags in the repository. /// public TagCollection Tags { @@ -302,7 +416,7 @@ public StashCollection Stashes } /// - /// Provides high level information about this repository. + /// Provides high level information about this repository. /// public RepositoryInformation Info { @@ -310,7 +424,7 @@ public RepositoryInformation Info } /// - /// 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. + /// 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. /// public Diff Diff { @@ -318,28 +432,43 @@ public Diff Diff } /// - /// Lookup notes in the repository. + /// Lookup notes in the repository. /// public NoteCollection Notes { get { return notes; } } + /// + /// Submodules in the repository. + /// + public SubmoduleCollection Submodules + { + get { return submodules; } + } + + /// + /// Worktrees in the repository. + /// + public WorktreeCollection Worktrees + { + get { return worktrees; } + } + #region IDisposable Members /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// public void Dispose() { Dispose(true); - GC.SuppressFinalize(this); } /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// - protected virtual void Dispose(bool disposing) + private void Dispose(bool disposing) { CleanupDisposableDependencies(); } @@ -347,66 +476,112 @@ protected virtual void Dispose(bool disposing) #endregion /// - /// Initialize a repository at the specified . + /// 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. - /// Overrides to the way a repository is opened. - /// a new instance of the class. The client code is responsible for calling on this instance. - public static Repository Init(string path, bool isBare = false, RepositoryOptions options = null) + /// 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) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - using (RepositorySafeHandle repo = Proxy.git_repository_init(path, isBare)) + using (RepositoryHandle repo = Proxy.git_repository_init_ext(null, path, isBare)) + { + FilePath repoPath = Proxy.git_repository_path(repo); + return repoPath.Native; + } + } + + /// + /// Initialize a repository by explictly setting the path to both the working directory and the git directory. + /// + /// The path to the working directory. + /// The path to the git repository to be created. + /// The path to the created repository. + public static string Init(string workingDirectoryPath, string gitDirectoryPath) + { + Ensure.ArgumentNotNullOrEmptyString(workingDirectoryPath, "workingDirectoryPath"); + Ensure.ArgumentNotNullOrEmptyString(gitDirectoryPath, "gitDirectoryPath"); + + // When being passed a relative workdir path, libgit2 will evaluate it from the + // path to the repository. We pass a fully rooted path in order for the LibGit2Sharp caller + // to pass a path relatively to his current directory. + string wd = Path.GetFullPath(workingDirectoryPath); + + // TODO: Shouldn't we ensure that the working folder isn't under the gitDir? + + using (RepositoryHandle repo = Proxy.git_repository_init_ext(wd, gitDirectoryPath, false)) { FilePath repoPath = Proxy.git_repository_path(repo); - return new Repository(repoPath.Native, options); + return repoPath.Native; } } /// - /// Try to lookup an object by its and . If no matching object is found, null will be returned. + /// Try to lookup an object by its . If no matching object is found, null will be returned. /// - /// The id to lookup. - /// The kind of GitObject being looked up - /// The or null if it was not found. - public GitObject Lookup(ObjectId id, GitObjectType type = GitObjectType.Any) + /// The id to lookup. + /// The or null if it was not found. + public GitObject Lookup(ObjectId id) { - return LookupInternal(id, type, null); + return LookupInternal(id, GitObjectType.Any, null); } - internal GitObject LookupInternal(ObjectId id, GitObjectType type, FilePath knownPath) + /// + /// Try to lookup an object by its sha or a reference canonical name. If no matching object is found, null will be returned. + /// + /// A revparse spec for the object to lookup. + /// The or null if it was not found. + public GitObject Lookup(string objectish) { - Ensure.ArgumentNotNull(id, "id"); + return Lookup(objectish, GitObjectType.Any, LookUpOptions.None); + } - GitObjectSafeHandle obj = null; + /// + /// Try to lookup an object by its and . If no matching object is found, null will be returned. + /// + /// The id to lookup. + /// The kind of GitObject being looked up + /// The or null if it was not found. + public GitObject Lookup(ObjectId id, ObjectType type) + { + return LookupInternal(id, type.ToGitObjectType(), null); + } - try - { - obj = Proxy.git_object_lookup(handle, id, type); + /// + /// Try to lookup an object by its sha or a reference canonical name and . If no matching object is found, null will be returned. + /// + /// A revparse spec for the object to lookup. + /// The kind of being looked up + /// The or null if it was not found. + public GitObject Lookup(string objectish, ObjectType type) + { + return Lookup(objectish, type.ToGitObjectType(), LookUpOptions.None); + } + + internal GitObject LookupInternal(ObjectId id, GitObjectType type, string knownPath) + { + Ensure.ArgumentNotNull(id, "id"); - if (obj == null) + using (ObjectHandle obj = Proxy.git_object_lookup(handle, id, type)) + { + if (obj == null || obj.IsInvalid) { return null; } return GitObject.BuildFrom(this, id, Proxy.git_object_type(obj), knownPath); } - finally - { - obj.SafeDispose(); - } - } - - /// - /// Try to lookup an object by its sha or a reference canonical name and . If no matching object is found, null will be returned. - /// - /// A revparse spec for the object to lookup. - /// The kind of being looked up - /// The or null if it was not found. - public GitObject Lookup(string objectish, GitObjectType type = GitObjectType.Any) - { - return Lookup(objectish, type, LookUpOptions.None); } private static string PathFromRevparseSpec(string spec) @@ -427,10 +602,10 @@ private static string PathFromRevparseSpec(string spec) internal GitObject Lookup(string objectish, GitObjectType type, LookUpOptions lookUpOptions) { - Ensure.ArgumentNotNullOrEmptyString(objectish, "commitOrBranchSpec"); + 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) { @@ -454,268 +629,536 @@ 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; } - /// - /// Lookup a commit by its SHA or name, or throw if a commit is not found. - /// - /// A revparse spec for the commit. - /// The commit. 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); } /// - /// Probe for a git repository. - /// The lookup start from and walk upward parent directories if nothing has been found. + /// Lists the Remote Repository References. /// - /// The base path where the lookup starts. - /// The path to the git repository. - public static string Discover(string startingPath) + /// + /// 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) { - FilePath discoveredPath = Proxy.git_repository_discover(startingPath); - - if (discoveredPath == null) - { - return null; - } - - return discoveredPath.Native; + return ListRemoteReferences(url, null, new ProxyOptions()); } /// - /// Clone with specified options. + /// Lists the Remote Repository References. /// - /// URI for the remote repository - /// Local path to clone into - /// True will result in a bare clone, false a full clone. - /// If true, the origin's HEAD will be checked out. This only applies - /// to non-bare repositories. - /// Handler for network transfer and indexing progress information - /// Handler for checkout progress information - /// Overrides to the way a repository is opened. - /// Credentials to use for user/pass authentication - /// - public static Repository Clone(string sourceUrl, string workdirPath, - bool bare = false, - bool checkout = true, - TransferProgressHandler onTransferProgress = null, - CheckoutProgressHandler onCheckoutProgress = null, - RepositoryOptions options = null, - Credentials credentials = null) - { - var cloneOpts = new GitCloneOptions - { - Bare = bare ? 1 : 0, - TransferProgressCallback = TransferCallbacks.GenerateCallback(onTransferProgress), - CheckoutOpts = - { - version = 1, - progress_cb = - CheckoutCallbacks.GenerateCheckoutCallbacks(onCheckoutProgress), - checkout_strategy = checkout - ? CheckoutStrategy.GIT_CHECKOUT_SAFE_CREATE - : CheckoutStrategy.GIT_CHECKOUT_NONE - }, - }; - - if (credentials != null) - { - cloneOpts.CredAcquireCallback = - (out IntPtr cred, IntPtr url, IntPtr username_from_url, uint types, IntPtr payload) => - NativeMethods.git_cred_userpass_plaintext_new(out cred, credentials.Username, credentials.Password); - } - - using(Proxy.git_clone(sourceUrl, workdirPath, cloneOpts)) {} - - // To be safe, make sure the credential callback is kept until - // alive until at least this point. - GC.KeepAlive(cloneOpts.CredAcquireCallback); - - return new Repository(workdirPath, options); + /// 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); } /// - /// Checkout the specified , reference or SHA. + /// Lists the Remote Repository References. /// - /// A revparse spec for the commit or branch to checkout. - /// controlling checkout behavior. - /// that checkout progress is reported through. - /// The that was checked out. - public Branch Checkout(string committishOrBranchSpec, CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress) + /// + /// 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) { - Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); - - var branch = Branches[committishOrBranchSpec]; - - if (branch != null) - { - return Checkout(branch, checkoutOptions, onCheckoutProgress); - } - - Commit commit = LookupCommit(committishOrBranchSpec); - CheckoutTree(commit.Tree, checkoutOptions, onCheckoutProgress); - - // Update HEAD. - Refs.UpdateTarget("HEAD", commit.Id.Sha); - - return Head; + return ListRemoteReferences(url, credentialsProvider, new ProxyOptions()); } /// - /// 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. + /// Lists the Remote Repository References. /// - /// The to check out. - /// controlling checkout behavior. - /// that checkout progress is reported through. - /// The that was checked out. - public Branch Checkout(Branch branch, CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress) + /// + /// 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(branch, "branch"); + Ensure.ArgumentNotNull(url, "url"); - // Make sure this is not an unborn branch. - if (branch.Tip == null) - { - throw new OrphanedHeadException( - string.Format(CultureInfo.InvariantCulture, - "The tip of branch '{0}' is null. There's nothing to checkout.", branch.Name)); - } + proxyOptions ??= new(); - CheckoutTree(branch.Tip.Tree, checkoutOptions, onCheckoutProgress); + using RepositoryHandle repositoryHandle = Proxy.git_repository_new(); + using RemoteHandle remoteHandle = Proxy.git_remote_create_anonymous(repositoryHandle, url); + using var proxyOptionsWrapper = new GitProxyOptionsWrapper(proxyOptions.CreateGitProxyOptions()); - // Update HEAD. - if (!branch.IsRemote && - string.Equals(Refs[branch.CanonicalName].TargetIdentifier, branch.Tip.Id.Sha, - StringComparison.OrdinalIgnoreCase)) - { - Refs.UpdateTarget("HEAD", branch.CanonicalName); - } - else + var gitCallbacks = new GitRemoteCallbacks { version = 1 }; + + if (credentialsProvider != null) { - Refs.UpdateTarget("HEAD", branch.Tip.Id.Sha); + var callbacks = new RemoteCallbacks(credentialsProvider); + gitCallbacks = callbacks.GenerateCallbacks(); } - return Head; + var gitProxyOptions = proxyOptionsWrapper.Options; + + Proxy.git_remote_connect(remoteHandle, GitDirection.Fetch, ref gitCallbacks, ref gitProxyOptions); + return Proxy.git_remote_ls(null, remoteHandle); } /// - /// 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. + /// Probe for a git repository. + /// The lookup start from and walk upward parent directories if nothing has been found. /// - /// The to checkout. - /// controlling checkout behavior. - /// that checkout progress is reported through. - private void CheckoutTree(Tree tree, CheckoutOptions checkoutOptions, CheckoutProgressHandler onCheckoutProgress) + /// The base path where the lookup starts. + /// The path to the git repository, or null if no repository was found. + public static string Discover(string startingPath) { - GitCheckoutOpts options = new GitCheckoutOpts - { - version = 1, - checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_SAFE, - progress_cb = CheckoutCallbacks.GenerateCheckoutCallbacks(onCheckoutProgress) - }; + FilePath discoveredPath = Proxy.git_repository_discover(startingPath); - if (checkoutOptions.HasFlag(CheckoutOptions.Force)) + if (discoveredPath == null) { - options.checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_FORCE; + return null; } - Proxy.git_checkout_tree(this.Handle, tree.Id, ref options); + return discoveredPath.Native; } /// - /// Sets the current to the specified commit and optionally resets the and - /// the content of the working tree to match. + /// Clone using default options. /// - /// Flavor of reset operation to perform. - /// The target commit object. - public void Reset(ResetOptions resetOptions, Commit commit) + /// 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) { - Ensure.ArgumentNotNull(commit, "commit"); - - Proxy.git_reset(handle, commit.Id, resetOptions); + return Clone(sourceUrl, workdirPath, null); } /// - /// Replaces entries in the with entries from the specified commit. + /// Clone with specified options. /// - /// The target commit object. - /// The list of paths (either files or directories) that should be considered. - public void Reset(Commit commit, IEnumerable paths = null) + /// 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) { - if (Info.IsBare) - { - throw new BareRepositoryException("Reset is not allowed in a bare repository"); - } + Ensure.ArgumentNotNull(sourceUrl, "sourceUrl"); + Ensure.ArgumentNotNull(workdirPath, "workdirPath"); - Ensure.ArgumentNotNull(commit, "commit"); + options ??= new CloneOptions(); - TreeChanges changes = Diff.Compare(commit.Tree, DiffTargets.Index, paths); - Index.Reset(changes); - } + // context variable that contains information on the repository that + // we are cloning. + var context = new RepositoryOperationContext(Path.GetFullPath(workdirPath), sourceUrl); - /// - /// 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 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. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - public Commit Commit(string message, Signature author, Signature committer, bool amendPreviousCommit = false) - { - if (amendPreviousCommit && Info.IsHeadOrphaned) + // Notify caller that we are starting to work with the current repository. + bool continueOperation = OnRepositoryOperationStarting(options.FetchOptions.RepositoryOperationStarting, context); + + if (!continueOperation) { - throw new LibGit2SharpException("Can not amend anything. The Head doesn't point at any commit."); + throw new UserCancelledException("Clone cancelled by the user."); } - var treeId = Proxy.git_tree_create_fromindex(Index); - var tree = this.Lookup(treeId); + using (var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + using (var fetchOptionsWrapper = new GitFetchOptionsWrapper()) + { + var gitCheckoutOptions = checkoutOptionsWrapper.Options; - var parents = RetrieveParentsOfTheCommitBeingCreated(amendPreviousCommit); + var gitFetchOptions = fetchOptionsWrapper.Options; + gitFetchOptions.Depth = options.FetchOptions.Depth; + gitFetchOptions.ProxyOptions = options.FetchOptions.ProxyOptions.CreateGitProxyOptions(); + gitFetchOptions.RemoteCallbacks = new RemoteCallbacks(options.FetchOptions).GenerateCallbacks(); - Commit result = ObjectDatabase.CreateCommit(message, author, committer, tree, parents, "HEAD"); + if (options.FetchOptions != null && options.FetchOptions.CustomHeaders != null) + { + gitFetchOptions.CustomHeaders = GitStrArrayManaged.BuildFrom(options.FetchOptions.CustomHeaders); + } - Proxy.git_repository_merge_cleanup(handle); + var cloneOpts = new GitCloneOptions + { + Version = 1, + Bare = options.IsBare ? 1 : 0, + CheckoutOpts = gitCheckoutOptions, + FetchOpts = gitFetchOptions, + }; - return result; - } + string clonedRepoPath; - private IEnumerable RetrieveParentsOfTheCommitBeingCreated(bool amendPreviousCommit) - { - if (amendPreviousCommit) - { - return Head.Tip.Parents; - } + try + { + cloneOpts.CheckoutBranch = StrictUtf8Marshaler.FromManaged(options.BranchName); - if (Info.IsHeadOrphaned) - { - return Enumerable.Empty(); - } + using (RepositoryHandle repo = Proxy.git_clone(sourceUrl, workdirPath, ref cloneOpts)) + { + clonedRepoPath = Proxy.git_repository_path(repo).Native; + } + } + finally + { + EncodingMarshaler.Cleanup(cloneOpts.CheckoutBranch); + } - var parents = new List { Head.Tip }; + // Notify caller that we are done with the current repository. + OnRepositoryOperationCompleted(options.FetchOptions.RepositoryOperationCompleted, context); - if (Info.CurrentOperation == CurrentOperation.Merge) - { - parents.AddRange(MergeHeads.Select(mh => mh.Tip)); - } + // 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 parents; + return clonedRepoPath; + } + } + + /// + /// Recursively clone submodules if directed to do so by the clone options. + /// + /// 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) + { + if (options.RecurseSubmodules) + { + List submodules = new List(); + + using (Repository repo = new Repository(repoPath)) + { + var updateOptions = new SubmoduleUpdateOptions() + { + 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)); + } + } + + // 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); + } + } + } + + /// + /// If a callback has been provided to notify callers that we are + /// either starting to work on a repository. + /// + /// 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) + { + bool continueOperation = true; + if (repositoryChangedCallback != null) + { + continueOperation = repositoryChangedCallback(context); + } + + return continueOperation; + } + + private static void OnRepositoryOperationCompleted( + RepositoryOperationCompleted repositoryChangedCallback, + RepositoryOperationContext context) + { + if (repositoryChangedCallback != null) + { + repositoryChangedCallback(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()); + } + + /// + /// Checkout the specified tree. + /// + /// The to checkout. + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + public void Checkout(Tree tree, IEnumerable paths, CheckoutOptions options) + { + CheckoutTree(tree, paths != null ? paths.ToList() : null, options); + } + + /// + /// Checkout the specified tree. + /// + /// The to checkout. + /// The paths to checkout. + /// Collection of parameters controlling checkout behavior. + private void CheckoutTree(Tree tree, IList paths, IConvertableToGitCheckoutOpts opts) + { + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts, ToFilePaths(paths))) + { + var options = checkoutOptionsWrapper.Options; + Proxy.git_checkout_tree(Handle, tree.Id, ref options); + } + } + + /// + /// Sets the current 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. + public void Reset(ResetMode resetMode, Commit commit) + { + Reset(resetMode, commit, new CheckoutOptions()); + } + + /// + /// 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. + public void Reset(ResetMode resetMode, Commit commit, CheckoutOptions opts) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(opts, "opts"); + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(opts)) + { + var options = checkoutOptionsWrapper.Options; + Proxy.git_reset(handle, commit.Id, resetMode, ref options); + } + } + + /// + /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. + /// + /// This method does not switch branches or update the current repository HEAD. + /// + /// + /// A revparse spec for the commit or branch to checkout paths from. + /// The paths to checkout. Will throw if null is passed in. Passing an empty enumeration results in nothing being checked out. + /// Collection of parameters controlling checkout behavior. + public void CheckoutPaths(string committishOrBranchSpec, IEnumerable paths, CheckoutOptions checkoutOptions) + { + Ensure.ArgumentNotNullOrEmptyString(committishOrBranchSpec, "committishOrBranchSpec"); + Ensure.ArgumentNotNull(paths, "paths"); + + var listOfPaths = paths.ToList(); + + // If there are no paths, then there is nothing to do. + if (listOfPaths.Count == 0) + { + return; + } + + Commit commit = LookupCommit(committishOrBranchSpec); + + CheckoutTree(commit.Tree, listOfPaths, checkoutOptions ?? new CheckoutOptions()); + } + + /// + /// 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 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 that specify the commit behavior. + /// The generated . + public Commit Commit(string message, Signature author, Signature committer, CommitOptions options) + { + if (options == null) + { + options = new CommitOptions(); + } + + bool isHeadOrphaned = Info.IsHeadUnborn; + + if (options.AmendPreviousCommit && isHeadOrphaned) + { + throw new UnbornBranchException("Can not amend anything. The Head doesn't point at any commit."); + } + + var treeId = Proxy.git_index_write_tree(Index.Handle); + var tree = this.Lookup(treeId); + + var parents = RetrieveParentsOfTheCommitBeingCreated(options.AmendPreviousCommit).ToList(); + + if (parents.Count == 1 && !options.AllowEmptyCommit) + { + var treesame = parents[0].Tree.Id.Equals(treeId); + var amendMergeCommit = options.AmendPreviousCommit && !isHeadOrphaned && Head.Tip.Parents.Count() > 1; + + if (treesame && !amendMergeCommit) + { + 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.")); + } + } + + Commit result = ObjectDatabase.CreateCommit(author, committer, message, tree, parents, options.PrettifyMessage, options.CommentaryChar); + + Proxy.git_repository_state_cleanup(handle); + + var logMessage = BuildCommitLogMessage(result, options.AmendPreviousCommit, isHeadOrphaned, parents.Count > 1); + UpdateHeadAndTerminalReference(result, logMessage); + + return result; + } + + private static string BuildCommitLogMessage(Commit commit, bool amendPreviousCommit, bool isHeadOrphaned, bool isMergeCommit) + { + string kind = string.Empty; + if (isHeadOrphaned) + { + kind = " (initial)"; + } + else if (amendPreviousCommit) + { + kind = " (amend)"; + } + else if (isMergeCommit) + { + kind = " (merge)"; + } + + return string.Format(CultureInfo.InvariantCulture, "commit{0}: {1}", kind, commit.MessageShort); + } + + private void UpdateHeadAndTerminalReference(Commit commit, string reflogMessage) + { + Reference reference = Refs.Head; + + while (true) //TODO: Implement max nesting level + { + if (reference is DirectReference) + { + Refs.UpdateTarget(reference, commit.Id, reflogMessage); + return; + } + + var symRef = (SymbolicReference)reference; + + reference = symRef.Target; + + if (reference == null) + { + Refs.Add(symRef.TargetIdentifier, commit.Id, reflogMessage); + return; + } + } + } + + private IEnumerable RetrieveParentsOfTheCommitBeingCreated(bool amendPreviousCommit) + { + if (amendPreviousCommit) + { + return Head.Tip.Parents; + } + + if (Info.IsHeadUnborn) + { + return Enumerable.Empty(); + } + + var parents = new List { Head.Tip }; + + if (Info.CurrentOperation == CurrentOperation.Merge) + { + parents.AddRange(MergeHeads.Select(mh => mh.Tip)); + } + + return parents; } /// /// Clean the working tree by removing files that are not under version control. /// - public virtual void RemoveUntrackedFiles() + public unsafe void RemoveUntrackedFiles() { var options = new GitCheckoutOpts { @@ -724,7 +1167,7 @@ public virtual 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() @@ -742,49 +1185,464 @@ internal T RegisterForCleanup(T disposable) where T : IDisposable } /// - /// 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) - /// + /// Merges changes from commit into the branch pointed at by HEAD. /// - public static string Version + /// The commit to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + public MergeResult Merge(Commit commit, Signature merger, MergeOptions options) { - get { return versionRetriever.Value; } + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(merger, "merger"); + + options = options ?? new MergeOptions(); + + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_lookup(Handle, commit.Id.Oid)) + { + return Merge(new[] { annotatedCommitHandle }, merger, options); + } } - private static string RetrieveVersion() + /// + /// Merges changes from branch into the branch pointed at by HEAD. + /// + /// The branch to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + public MergeResult Merge(Branch branch, Signature merger, MergeOptions options) { - Assembly assembly = typeof(Repository).Assembly; + Ensure.ArgumentNotNull(branch, "branch"); + Ensure.ArgumentNotNull(merger, "merger"); + + options = options ?? new MergeOptions(); + + using (ReferenceHandle referencePtr = Refs.RetrieveReferencePtr(branch.CanonicalName)) + using (AnnotatedCommitHandle annotatedCommitHandle = Proxy.git_annotated_commit_from_ref(Handle, referencePtr)) + { + return Merge(new[] { annotatedCommitHandle }, merger, options); + } + } - Version version = assembly.GetName().Version; + /// + /// Merges changes from the commit into the branch pointed at by HEAD. + /// + /// The commit to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// Specifies optional parameters controlling merge behavior; if null, the defaults are used. + /// The of the merge. + public MergeResult Merge(string committish, Signature merger, MergeOptions options) + { + Ensure.ArgumentNotNull(committish, "committish"); + Ensure.ArgumentNotNull(merger, "merger"); - string libgit2Hash = ReadContentFromResource(assembly, "libgit2_hash.txt"); - string libgit2sharpHash = ReadContentFromResource(assembly, "libgit2sharp_hash.txt"); + options = options ?? new MergeOptions(); - return string.Format( - CultureInfo.InvariantCulture, - "{0}-{1}-{2} ({3})", - version.ToString(3), - libgit2sharpHash.Substring(0, 7), - libgit2Hash.Substring(0, 7), - NativeMethods.ProcessorArchitecture - ); + Commit commit = LookupCommit(committish); + return Merge(commit, merger, options); } - private static string ReadContentFromResource(Assembly assembly, string partialResourceName) + /// + /// 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. + public MergeResult MergeFetchedRefs(Signature merger, MergeOptions options) { - string name = string.Format(CultureInfo.InvariantCulture, "LibGit2Sharp.{0}", partialResourceName); - using (var sr = new StreamReader(assembly.GetManifestResourceStream(name))) + Ensure.ArgumentNotNull(merger, "merger"); + + options = options ?? new MergeOptions(); + + // The current FetchHeads that are marked for merging. + FetchHead[] fetchHeads = Network.FetchHeads.Where(fetchHead => fetchHead.ForMerge).ToArray(); + + if (fetchHeads.Length == 0) + { + var expectedRef = this.Head.UpstreamBranchCanonicalName; + throw new MergeFetchHeadNotFoundException("The current branch is configured to merge with the reference '{0}' from the remote, but this reference was not fetched.", + expectedRef); + } + + AnnotatedCommitHandle[] annotatedCommitHandles = fetchHeads.Select(fetchHead => + Proxy.git_annotated_commit_from_fetchhead(Handle, fetchHead.RemoteCanonicalName, fetchHead.Url, fetchHead.Target.Id.Oid)).ToArray(); + + try { - return sr.ReadLine(); + // Perform the merge. + return Merge(annotatedCommitHandles, merger, options); + } + finally + { + // Cleanup. + foreach (AnnotatedCommitHandle annotatedCommitHandle in annotatedCommitHandles) + { + annotatedCommitHandle.Dispose(); + } } } /// - /// Gets the references to the tips that are currently being merged. + /// Revert the specified commit. + /// + /// If the revert is successful but there are no changes to commit, + /// then the will be . + /// If the revert is successful and there are changes to revert, then + /// the will be . + /// If the revert resulted in conflicts, then the + /// will be . + /// /// - public virtual IEnumerable MergeHeads + /// The to revert. + /// The of who is performing the revert. + /// controlling revert behavior. + /// The result of the revert. + public RevertResult Revert(Commit commit, Signature reverter, RevertOptions options) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(reverter, "reverter"); + + if (Info.IsHeadUnborn) + { + throw new UnbornBranchException("Can not revert the commit. The Head doesn't point at a commit."); + } + + options = options ?? new RevertOptions(); + + RevertResult result = null; + + using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + var mergeOptions = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + }; + + GitRevertOpts gitRevertOpts = new GitRevertOpts() + { + Mainline = (uint)options.Mainline, + MergeOpts = mergeOptions, + + CheckoutOpts = checkoutOptionsWrapper.Options, + }; + + Proxy.git_revert(handle, commit.Id.Oid, gitRevertOpts); + + if (Index.IsFullyMerged) + { + Commit revertCommit = null; + + // Check if the revert generated any changes + // and set the revert status accordingly + bool anythingToRevert = RetrieveStatus( + new StatusOptions() + { + DetectRenamesInIndex = false, + Show = StatusShowOption.IndexOnly + }).Any(); + + RevertStatus revertStatus = anythingToRevert ? + RevertStatus.Reverted : RevertStatus.NothingToRevert; + + if (options.CommitOnSuccess) + { + if (!anythingToRevert) + { + // If there were no changes to revert, and we are + // asked to commit the changes, then cleanup + // the repository state (following command line behavior). + Proxy.git_repository_state_cleanup(handle); + } + else + { + revertCommit = this.Commit( + Info.Message, + author: reverter, + committer: reverter, + options: null); + } + } + + result = new RevertResult(revertStatus, revertCommit); + } + else + { + result = new RevertResult(RevertStatus.Conflicts); + } + } + + return result; + } + + /// + /// Cherry-picks the specified commit. + /// + /// The to cherry-pick. + /// The of who is performing the cherry pick. + /// controlling cherry pick behavior. + /// The result of the cherry pick. + public CherryPickResult CherryPick(Commit commit, Signature committer, CherryPickOptions options) + { + Ensure.ArgumentNotNull(commit, "commit"); + Ensure.ArgumentNotNull(committer, "committer"); + + options = options ?? new CherryPickOptions(); + + CherryPickResult result = null; + + using (var checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options)) + { + var mergeOptions = new GitMergeOpts + { + Version = 1, + MergeFileFavorFlags = options.MergeFileFavor, + MergeTreeFlags = options.FindRenames ? GitMergeFlag.GIT_MERGE_FIND_RENAMES : + GitMergeFlag.GIT_MERGE_NORMAL, + RenameThreshold = (uint)options.RenameThreshold, + TargetLimit = (uint)options.TargetLimit, + }; + + var gitCherryPickOpts = new GitCherryPickOptions() + { + Mainline = (uint)options.Mainline, + MergeOpts = mergeOptions, + + CheckoutOpts = checkoutOptionsWrapper.Options, + }; + + Proxy.git_cherrypick(handle, commit.Id.Oid, gitCherryPickOpts); + + if (Index.IsFullyMerged) + { + Commit cherryPickCommit = null; + if (options.CommitOnSuccess) + { + cherryPickCommit = this.Commit(Info.Message, commit.Author, committer, null); + } + + result = new CherryPickResult(CherryPickStatus.CherryPicked, cherryPickCommit); + } + else + { + result = new CherryPickResult(CherryPickStatus.Conflicts); + } + } + + return result; + } + + private FastForwardStrategy FastForwardStrategyFromMergePreference(GitMergePreference preference) + { + switch (preference) + { + case GitMergePreference.GIT_MERGE_PREFERENCE_NONE: + return FastForwardStrategy.Default; + case GitMergePreference.GIT_MERGE_PREFERENCE_FASTFORWARD_ONLY: + return FastForwardStrategy.FastForwardOnly; + case GitMergePreference.GIT_MERGE_PREFERENCE_NO_FASTFORWARD: + return FastForwardStrategy.NoFastForward; + default: + throw new InvalidOperationException(string.Format("Unknown merge preference: {0}", preference)); + } + } + + /// + /// Internal implementation of merge. + /// + /// 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(AnnotatedCommitHandle[] annotatedCommits, Signature merger, MergeOptions options) + { + GitMergeAnalysis mergeAnalysis; + GitMergePreference mergePreference; + + Proxy.git_merge_analysis(Handle, annotatedCommits, out mergeAnalysis, out mergePreference); + + MergeResult mergeResult = null; + + if ((mergeAnalysis & GitMergeAnalysis.GIT_MERGE_ANALYSIS_UP_TO_DATE) == GitMergeAnalysis.GIT_MERGE_ANALYSIS_UP_TO_DATE) + { + return new MergeResult(MergeStatus.UpToDate); + } + + FastForwardStrategy fastForwardStrategy = (options.FastForwardStrategy != FastForwardStrategy.Default) ? + options.FastForwardStrategy : FastForwardStrategyFromMergePreference(mergePreference); + + switch (fastForwardStrategy) + { + case FastForwardStrategy.Default: + if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD)) + { + 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(annotatedCommits[0], options); + } + else if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL)) + { + mergeResult = NormalMerge(annotatedCommits, merger, options); + } + break; + case FastForwardStrategy.FastForwardOnly: + if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_FASTFORWARD)) + { + 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(annotatedCommits[0], options); + } + else + { + // TODO: Maybe this condition should rather be indicated through the merge result + // instead of throwing an exception. + throw new NonFastForwardException("Cannot perform fast-forward merge."); + } + break; + case FastForwardStrategy.NoFastForward: + if (mergeAnalysis.HasFlag(GitMergeAnalysis.GIT_MERGE_ANALYSIS_NORMAL)) + { + mergeResult = NormalMerge(annotatedCommits, merger, options); + } + break; + default: + throw new NotImplementedException( + string.Format(CultureInfo.InvariantCulture, "Unknown fast forward strategy: {0}", fastForwardStrategy)); + } + + if (mergeResult == null) + { + throw new NotImplementedException( + string.Format(CultureInfo.InvariantCulture, "Unknown merge analysis: {0}", mergeAnalysis)); + } + + return mergeResult; + } + + /// + /// Perform a normal merge (i.e. a non-fast-forward 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(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 = 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, annotatedCommits, mergeOptions, checkoutOpts, out earlyStop); + } + + if (earlyStop) + { + return new MergeResult(MergeStatus.Conflicts); + } + + if (Index.IsFullyMerged) + { + Commit mergeCommit = null; + if (options.CommitOnSuccess) + { + // Commit the merge + mergeCommit = Commit(Info.Message, author: merger, committer: merger, options: null); + } + + mergeResult = new MergeResult(MergeStatus.NonFastForward, mergeCommit); + } + else + { + mergeResult = new MergeResult(MergeStatus.Conflicts); + } + + return mergeResult; + } + + /// + /// Perform a fast-forward merge. + /// + /// The merge head handle to fast-forward merge. + /// Options controlling merge behavior. + /// The of the merge. + private MergeResult FastForwardMerge(AnnotatedCommitHandle annotatedCommit, MergeOptions options) + { + 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)); + + var reference = Refs.Head.ResolveToDirectReference(); + + // TODO: This reflog entry could be more specific + string refLogEntry = string.Format( + CultureInfo.InvariantCulture, "merge {0}: Fast-forward", fastForwardCommit.Sha); + + if (reference == null) + { + // Reference does not exist, create it. + Refs.Add(Refs.Head.TargetIdentifier, fastForwardCommit.Id, refLogEntry); + } + else + { + // Update target reference. + Refs.UpdateTarget(reference, fastForwardCommit.Id.Sha, refLogEntry); + } + + return new MergeResult(MergeStatus.FastForward, fastForwardCommit); + } + + /// + /// Gets the references to the tips that are currently being merged. + /// + internal IEnumerable MergeHeads { get { @@ -794,14 +1652,144 @@ public virtual IEnumerable MergeHeads } } + internal StringComparer PathComparer + { + get { return pathCase.Value.Comparer; } + } + + internal FilePath[] ToFilePaths(IEnumerable paths) + { + if (paths == null) + { + return null; + } + + var filePaths = new List(); + + foreach (string path in paths) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("At least one provided path is either null or empty.", nameof(paths)); + } + + filePaths.Add(this.BuildRelativePathFrom(path)); + } + + if (filePaths.Count == 0) + { + 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 4a8d3a9dd..5d0788c8a 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -1,257 +1,474 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; namespace LibGit2Sharp { /// - /// Provides helper overloads to a . + /// Provides helper overloads to a . /// public static class RepositoryExtensions { /// - /// Try to lookup an object by its sha or a reference name. + /// Try to lookup an object by its sha or a reference name. /// - /// - /// The being looked up. - /// The revparse spec for the object to lookup. - /// + /// The kind of to lookup. + /// The being looked up. + /// The revparse spec for the object to lookup. + /// The retrieved , or null if none was found. public static T Lookup(this IRepository repository, string objectish) where T : GitObject { - return (T)repository.Lookup(objectish, GitObject.TypeToTypeMap[typeof (T)]); + EnsureNoGitLink(); + + if (typeof(T) == typeof(GitObject)) + { + return (T)repository.Lookup(objectish); + } + + return (T)repository.Lookup(objectish, GitObject.TypeToKindMap[typeof(T)]); } /// - /// Try to lookup an object by its . + /// Try to lookup an object by its . /// - /// - /// The being looked up. - /// The id. - /// + /// The kind of to lookup. + /// The being looked up. + /// The id. + /// The retrieved , or null if none was found. public static T Lookup(this IRepository repository, ObjectId id) where T : GitObject { - return (T)repository.Lookup(id, GitObject.TypeToTypeMap[typeof(T)]); + EnsureNoGitLink(); + + if (typeof(T) == typeof(GitObject)) + { + return (T)repository.Lookup(id); + } + + return (T)repository.Lookup(id, GitObject.TypeToKindMap[typeof(T)]); + } + + private static void EnsureNoGitLink() where T : GitObject + { + if (typeof(T) != typeof(GitLink)) + { + return; + } + + throw new ArgumentException("A GitObject of type 'GitLink' cannot be looked up."); } /// - /// Creates a lightweight tag with the specified name. This tag will point at the commit pointed at by the . + /// Creates a lightweight tag with the specified name. This tag will point at the commit pointed at by the . /// - /// The being worked with. - /// The name of the tag to create. + /// The being worked with. + /// The name of the tag to create. public static Tag ApplyTag(this IRepository repository, string tagName) { - return ApplyTag(repository, tagName, repository.Head.CanonicalName); + return repository.Tags.Add(tagName, RetrieveHeadCommit(repository)); } /// - /// Creates a lightweight tag with the specified name. This tag will point at the . + /// Creates a lightweight tag with the specified name. This tag will point at the . /// - /// The being worked with. - /// The name of the tag to create. - /// The revparse spec for the target object. + /// The being worked with. + /// The name of the tag to create. + /// The revparse spec for the target object. public static Tag ApplyTag(this IRepository repository, string tagName, string objectish) { return repository.Tags.Add(tagName, objectish); } /// - /// Creates an annotated tag with the specified name. This tag will point at the commit pointed at by the . + /// Creates an annotated tag with the specified name. This tag will point at the commit pointed at by the . /// - /// The being worked with. - /// The name of the tag to create. - /// The identity of the creator of this tag. - /// The annotation message. + /// The being worked with. + /// The name of the tag to create. + /// The identity of the creator of this tag. + /// The annotation message. public static Tag ApplyTag(this IRepository repository, string tagName, Signature tagger, string message) { - return ApplyTag(repository, tagName, repository.Head.CanonicalName, tagger, message); + return repository.Tags.Add(tagName, RetrieveHeadCommit(repository), tagger, message); + } + + private static Commit RetrieveHeadCommit(IRepository repository) + { + Commit commit = repository.Head.Tip; + + Ensure.GitObjectIsNotNull(commit, "HEAD"); + + return commit; } /// - /// Creates an annotated tag with the specified name. This tag will point at the . + /// Creates an annotated tag with the specified name. This tag will point at the . /// - /// The being worked with. - /// The name of the tag to create. - /// The revparse spec for the target object. - /// The identity of the creator of this tag. - /// The annotation message. + /// The being worked with. + /// The name of the tag to create. + /// The revparse spec for the target object. + /// The identity of the creator of this tag. + /// The annotation message. public static Tag ApplyTag(this IRepository repository, string tagName, string objectish, Signature tagger, string message) { return repository.Tags.Add(tagName, objectish, tagger, message); } /// - /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . + /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . /// - /// The being worked with. - /// The name of the branch to create. + /// The being worked with. + /// The name of the branch to create. public static Branch CreateBranch(this IRepository repository, string branchName) { - return CreateBranch(repository, branchName, repository.Head.Tip); + var head = repository.Head; + var reflogName = head is DetachedHead ? head.Tip.Sha : head.FriendlyName; + + return CreateBranch(repository, branchName, reflogName); } /// - /// Creates a branch with the specified name. This branch will point at . + /// Creates a branch with the specified name. This branch will point at . /// - /// The being worked with. - /// The name of the branch to create. - /// The commit which should be pointed at by the Branch. + /// The being worked with. + /// The name of the branch to create. + /// The commit which should be pointed at by the Branch. public static Branch CreateBranch(this IRepository repository, string branchName, Commit target) { return repository.Branches.Add(branchName, target); } /// - /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . + /// Creates a branch with the specified name. This branch will point at the commit pointed at by the . /// - /// The being worked with. - /// The name of the branch to create. - /// The revparse spec for the target commit. + /// The being worked with. + /// The name of the branch to create. + /// The revparse spec for the target commit. public static Branch CreateBranch(this IRepository repository, string branchName, string committish) { return repository.Branches.Add(branchName, committish); } /// - /// Sets the current /> to the specified commit and optionally resets the and - /// the content of the working tree to match. + /// 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. - public static void Reset(this IRepository repository, ResetOptions resetOptions, string committish = "HEAD") + /// The being worked with. + /// Flavor of reset operation to perform. + public static void Reset(this IRepository repository, ResetMode resetMode) + { + repository.Reset(resetMode, "HEAD"); + } + + /// + /// 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. + public static void Reset(this IRepository repository, ResetMode resetMode, string committish) { Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); Commit commit = LookUpCommit(repository, committish); - repository.Reset(resetOptions, commit); + repository.Reset(resetMode, commit); + } + + private static Commit LookUpCommit(IRepository repository, string committish) + { + GitObject obj = repository.Lookup(committish); + Ensure.GitObjectIsNotNull(obj, committish); + return obj.Peel(true); } /// - /// Replaces entries in the with entries from the specified commit. + /// 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. - /// A revparse spec for the target commit object. - /// The list of paths (either files or directories) that should be considered. - public static void Reset(this IRepository repository, string committish = "HEAD", IEnumerable paths = null) + /// 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) { - if (repository.Info.IsBare) + return repository.Commit(message, author, committer, default(CommitOptions)); + } + + internal static string BuildRelativePathFrom(this IRepository repo, string path) + { + //TODO: To be removed when libgit2 natively implements this + if (!Path.IsPathRooted(path)) { - throw new BareRepositoryException("Reset is not allowed in a bare repository"); + return path; } - Ensure.ArgumentNotNullOrEmptyString(committish, "committish"); + string normalizedPath = Path.GetFullPath(path); - Commit commit = LookUpCommit(repository, committish); + 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)); + } - repository.Reset(commit, paths); + return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); } - private static Commit LookUpCommit(IRepository repository, string committish) + internal static bool PathStartsWith(IRepository repository, string path, string value) { - GitObject obj = repository.Lookup(committish); - Ensure.GitObjectIsNotNull(obj, committish); - return obj.DereferenceToCommit(true); + var pathCase = new PathCase(repository); + return pathCase.StartsWith(path, value); + } + + private static ObjectId DereferenceToCommit(Repository repo, string identifier) + { + var options = LookUpOptions.DereferenceResultToCommit; + + if (!AllowOrphanReference(repo, identifier)) + { + options |= LookUpOptions.ThrowWhenNoGitObjectHasBeenFound; + } + + // TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees? + GitObject commit = repo.Lookup(identifier, GitObjectType.Any, options); + + return commit != null ? commit.Id : null; + } + + private static bool AllowOrphanReference(IRepository repo, string identifier) + { + return string.Equals(identifier, "HEAD", StringComparison.Ordinal) + || string.Equals(identifier, repo.Head.CanonicalName, StringComparison.Ordinal); + } + + private static ObjectId SingleCommittish(this Repository repo, object identifier) + { + if (ReferenceEquals(identifier, null)) + { + return null; + } + + if (identifier is string) + { + return DereferenceToCommit(repo, (string)identifier); + } + + if (identifier is ObjectId) + { + return DereferenceToCommit(repo, ((ObjectId)identifier).Sha); + } + + if (identifier is Commit) + { + return ((Commit)identifier).Id; + } + + if (identifier is TagAnnotation) + { + return DereferenceToCommit(repo, ((TagAnnotation)identifier).Target.Id.Sha); + } + + if (identifier is Tag) + { + return DereferenceToCommit(repo, ((Tag)identifier).Target.Id.Sha); + } + + var branch = identifier as Branch; + if (branch != null) + { + if (branch.Tip != null || !branch.IsCurrentRepositoryHead) + { + Ensure.GitObjectIsNotNull(branch.Tip, branch.CanonicalName); + return branch.Tip.Id; + } + } + + if (identifier is Reference) + { + return DereferenceToCommit(repo, ((Reference)identifier).CanonicalName); + } + + return null; + } + + /// + /// Dereferences the passed identifier to a commit. If the identifier is enumerable, all items are dereferenced. + /// + /// Repository to search + /// Committish to dereference + /// If true, allow thrown exceptions to propagate. If false, exceptions will be swallowed and null returned. + /// A series of commit s which identify commit objects. + internal static IEnumerable Committishes(this Repository repo, object identifier, bool throwIfNotFound = false) + { + var singleReturnValue = repo.SingleCommittish(identifier); + + if (singleReturnValue != null) + { + yield return singleReturnValue; + yield break; + } + + if (identifier is IEnumerable) + { + foreach (object entry in (IEnumerable)identifier) + { + foreach (ObjectId oid in Committishes(repo, entry)) + { + yield return oid; + } + } + + yield break; + } + + if (throwIfNotFound) + { + throw new LibGit2SharpException("Unexpected kind of identifier '{0}'.", identifier); + } + + yield return 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. - /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. + /// Dereference the identifier to a commit. If the identifier is enumerable, dereference the first element. /// - /// The being worked with. - /// The description of why a change was made to the repository. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - public static Commit Commit(this IRepository repository, string message, bool amendPreviousCommit = false) + /// The to search + /// Committish to dereference + /// An for a commit object. + internal static ObjectId Committish(this Repository repo, object identifier) { - Signature author = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); + return repo.Committishes(identifier, true).First(); + } - return repository.Commit(message, author, amendPreviousCommit); + /// + /// Merges changes from branch into the branch pointed at by HEAD. + /// + /// The being worked with. + /// The branch to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// The of the merge. + public static MergeResult Merge(this IRepository repository, Branch branch, Signature merger) + { + return repository.Merge(branch, merger, 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 Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. + /// Merges changes from the commit into the branch pointed at by HEAD. /// - /// The being worked with. - /// The of who made the change. - /// The description of why a change was made to the repository. - /// True to amend the current pointed at by , false otherwise. - /// The generated . - public static Commit Commit(this IRepository repository, string message, Signature author, bool amendPreviousCommit = false) + /// The being worked with. + /// The commit to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// The of the merge. + public static MergeResult Merge(this IRepository repository, string committish, Signature merger) { - Signature committer = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); + return repository.Merge(committish, merger, null); + } - return repository.Commit(message, author, committer, amendPreviousCommit); + /// + /// Updates specifed paths in the index and working directory with the versions from the specified branch, reference, or SHA. + /// + /// This method does not switch branches or update the current repository HEAD. + /// + /// + /// The being worked with. + /// A revparse spec for the commit or branch to checkout paths from. + /// The paths to checkout. Will throw if null is passed in. Passing an empty enumeration results in nothing being checked out. + public static void CheckoutPaths(this IRepository repository, string committishOrBranchSpec, IEnumerable paths) + { + repository.CheckoutPaths(committishOrBranchSpec, paths, null); } /// - /// Fetch from the specified remote. + /// Sets the current to the specified commit and optionally resets the and + /// the content of the working tree to match. /// - /// The being worked with. - /// The name of the to fetch from. - /// Optional parameter indicating what tags to download. - /// Progress callback. Corresponds to libgit2 progress callback. - /// Completion callback. Corresponds to libgit2 completion callback. - /// UpdateTips callback. Corresponds to libgit2 update_tips callback. - /// Callback method that transfer progress will be reported through. - /// Reports the client's state regarding the received and processed (bytes, objects) from the server. - /// Credentials to use for username/password authentication. - public static void Fetch(this IRepository repository, string remoteName, - TagFetchMode tagFetchMode = TagFetchMode.Auto, - ProgressHandler onProgress = null, - CompletionHandler onCompletion = null, - UpdateTipsHandler onUpdateTips = null, - TransferProgressHandler onTransferProgress = null, - Credentials credentials = null) - { - Ensure.ArgumentNotNull(repository, "repository"); - Ensure.ArgumentNotNullOrEmptyString(remoteName, "remoteName"); - - Remote remote = repository.Network.Remotes.RemoteForName(remoteName, true); - remote.Fetch(tagFetchMode, onProgress, onCompletion, onUpdateTips, - onTransferProgress, credentials); - } - - private static Signature BuildSignatureFromGlobalConfiguration(IRepository repository, DateTimeOffset now) - { - var name = repository.Config.Get("user.name"); - var email = repository.Config.Get("user.email"); - - if ((name == null) || (email == null)) - { - throw new LibGit2SharpException("Can not find Name and Email settings of the current user in Git configuration."); - } + /// The being worked with. + /// Flavor of reset operation to perform. + /// The target commit object. + public static void Reset(this IRepository repository, ResetMode resetMode, Commit commit) + { + repository.Reset(resetMode, commit); + } - return new Signature(name.Value, email.Value, now); + /// + /// Find where each line of a file originated. + /// + /// The being worked with. + /// Path of the file to blame. + /// The blame for the file. + public static BlameHunkCollection Blame(this IRepository repository, string path) + { + return repository.Blame(path, null); + } + + /// + /// Cherry-picks the specified commit. + /// + /// The being worked with. + /// The to cherry-pick. + /// The of who is performing the cherry pick. + /// The result of the cherry pick. + public static CherryPickResult CherryPick(this IRepository repository, Commit commit, Signature committer) + { + return repository.CherryPick(commit, committer, null); } /// - /// Checkout the specified , reference or SHA. + /// Merges changes from commit into the branch pointed at by HEAD. /// - /// The being worked with. - /// A revparse spec for the commit or branch to checkout. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, string commitOrBranchSpec) + /// The being worked with. + /// The commit to merge into the branch pointed at by HEAD. + /// The of who is performing the merge. + /// The of the merge. + public static MergeResult Merge(this IRepository repository, Commit commit, Signature merger) { - return repository.Checkout(commitOrBranchSpec, CheckoutOptions.None, null); + return repository.Merge(commit, merger, null); } + /// + /// Revert the specified commit. + /// + /// The being worked with. + /// The to revert. + /// The of who is performing the revert. + /// The result of the revert. + public static RevertResult Revert(this IRepository repository, Commit commit, Signature reverter) + { + 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); + } /// - /// Checkout the specified . + /// 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 to check out. - /// The that was checked out. - public static Branch Checkout(this IRepository repository, Branch branch) + /// 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.Checkout(branch, CheckoutOptions.None, null); + return repository.Describe(commit, new DescribeOptions()); } } } diff --git a/LibGit2Sharp/RepositoryInformation.cs b/LibGit2Sharp/RepositoryInformation.cs index 8c055d83b..436b3198e 100644 --- a/LibGit2Sharp/RepositoryInformation.cs +++ b/LibGit2Sharp/RepositoryInformation.cs @@ -1,17 +1,16 @@ -using System; -using LibGit2Sharp.Core; +using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Provides high level information about a repository. + /// Provides high level information about a repository. /// public class RepositoryInformation { private readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected RepositoryInformation() { } @@ -24,42 +23,36 @@ 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); } /// - /// Gets the normalized path to the git repository. + /// Gets the normalized path to the git repository. /// public virtual string Path { get; private set; } /// - /// Gets the normalized path to the working directory. - /// - /// Is the repository is bare, null is returned. - /// + /// Gets the normalized path to the working directory. + /// + /// If the repository is bare, null is returned. + /// /// public virtual string WorkingDirectory { get; private set; } /// - /// Indicates whether the repository has a working directory. + /// Indicates whether the repository has a working directory. /// public virtual bool IsBare { get; private set; } /// - /// Gets a value indicating whether this repository is empty. + /// Indicates whether the repository is shallow (the result of `git clone --depth ...`) /// - /// - /// true if this repository is empty; otherwise, false. - /// - [Obsolete("This method will be removed in the next release.")] - public virtual bool IsEmpty - { - get { return Proxy.git_repository_is_empty(repo.Handle); } - } + public virtual bool IsShallow { get; private set; } /// - /// Indicates whether the Head points to an arbitrary commit instead of the tip of a local branch. + /// Indicates whether the Head points to an arbitrary commit instead of the tip of a local branch. /// public virtual bool IsHeadDetached { @@ -67,15 +60,15 @@ public virtual bool IsHeadDetached } /// - /// Indicates whether the Head points to a reference which doesn't exist. + /// Indicates whether the Head points to a reference which doesn't exist. /// - public virtual bool IsHeadOrphaned + public virtual bool IsHeadUnborn { - get { return Proxy.git_repository_head_orphan(repo.Handle); } + get { return Proxy.git_repository_head_unborn(repo.Handle); } } /// - /// The pending interactive operation. + /// The pending interactive operation. /// public virtual CurrentOperation CurrentOperation { @@ -83,7 +76,7 @@ public virtual CurrentOperation CurrentOperation } /// - /// The message for a pending interactive operation. + /// The message for a pending interactive operation. /// public virtual string Message { diff --git a/LibGit2Sharp/RepositoryNotFoundException.cs b/LibGit2Sharp/RepositoryNotFoundException.cs index edc6fcdf2..e2bc63d8b 100644 --- a/LibGit2Sharp/RepositoryNotFoundException.cs +++ b/LibGit2Sharp/RepositoryNotFoundException.cs @@ -1,49 +1,60 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif 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. + /// 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. + /// Initializes a new instance of the class. /// public RepositoryNotFoundException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// A message that describes the error. public RepositoryNotFoundException(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. + /// Initializes a new instance of the class with a specified error message. /// - /// 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. + /// 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. + /// + /// 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 RepositoryNotFoundException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 846477e94..55692a663 100644 --- a/LibGit2Sharp/RepositoryOptions.cs +++ b/LibGit2Sharp/RepositoryOptions.cs @@ -1,56 +1,41 @@ -namespace LibGit2Sharp +using System; + +namespace LibGit2Sharp { /// - /// Provides optional additional information to the Repository to be opened. + /// Provides optional additional information to the Repository to be opened. /// - public class RepositoryOptions + public sealed class RepositoryOptions { /// - /// Overrides the probed location of the working directory of a standard repository, - /// or, combined with , would - /// allow to work against a bare repository as it was a standard one. - /// - /// The path has to lead to an existing directory. - /// + /// Overrides the probed location of the working directory of a standard repository, + /// or, combined with , would + /// allow to work against a bare repository as it was a standard one. + /// + /// The path has to lead to an existing directory. + /// /// public string WorkingDirectoryPath { get; set; } /// - /// Overrides the probed location of the Index file of a standard repository, - /// or, combined with , would - /// allow to work against a bare repository as it was a standard one. - /// - /// The path has either to lead to an existing valid Index file, - /// or to a non existent Index file which will be eventually created. - /// + /// Overrides the probed location of the Index file of a standard repository, + /// or, combined with , would + /// allow to work against a bare repository as it was a standard one. + /// + /// The path has either to lead to an existing valid Index file, + /// or to a non existent Index file which will be eventually created. + /// /// 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. - /// - /// The path has to lead to an existing valid configuration file, - /// or to a non existent configuration file which will be eventually created. - /// + /// Overrides the default identity to be used when creating reflog entries. + /// + /// 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 2a8839fbb..cc1c6e7e0 100644 --- a/LibGit2Sharp/RepositoryStatus.cs +++ b/LibGit2Sharp/RepositoryStatus.cs @@ -5,77 +5,194 @@ using System.Globalization; using System.Linq; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// Holds the result of the determination of the state of the working directory. - /// Only files that differ from the current index and/or commit will be considered. + /// Holds the result of the determination of the state of the working directory. + /// Only files that differ from the current index and/or commit will be considered. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class RepositoryStatus : IEnumerable { private readonly ICollection statusEntries; - private readonly List added = new List(); - private readonly List staged = new List(); - private readonly List removed = new List(); - private readonly List missing = new List(); - private readonly List modified = new List(); - private readonly List untracked = new List(); - private readonly List ignored = new List(); + private readonly List added = new List(); + private readonly List staged = new List(); + private readonly List removed = new List(); + private readonly List missing = new List(); + private readonly List modified = new List(); + private readonly List untracked = new List(); + 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(); + private readonly IDictionary> dispatcher = Build(); - private static IDictionary> Build() + 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.Ignored, (rs, s) => rs.ignored.Add(s) }, - }; + return new Dictionary> + { + { 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) }, + }; } /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected RepositoryStatus() { } - internal RepositoryStatus(Repository repo) + internal unsafe RepositoryStatus(Repository repo, StatusOptions options) + { + statusEntries = new List(); + + using (GitStatusOptions coreOptions = CreateStatusOptions(options ?? new StatusOptions())) + 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++) + { + 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 && entry.State != FileStatus.Unaltered); + } + } + + private static GitStatusOptions CreateStatusOptions(StatusOptions options) { - statusEntries = Proxy.git_status_foreach(repo.Handle, StateChanged); - isDirty = statusEntries.Any(entry => entry.State != FileStatus.Ignored); + var coreOptions = new GitStatusOptions + { + Version = 1, + Show = (GitStatusShow)options.Show, + }; + + if (options.IncludeIgnored) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeIgnored; + } + + if (options.IncludeUntracked) + { + coreOptions.Flags |= GitStatusOptionFlags.IncludeUntracked; + } + + if (options.DetectRenamesInIndex) + { + coreOptions.Flags |= + GitStatusOptionFlags.RenamesHeadToIndex | + GitStatusOptionFlags.RenamesFromRewrites; + } + + if (options.DetectRenamesInWorkDir) + { + coreOptions.Flags |= + GitStatusOptionFlags.RenamesIndexToWorkDir | + GitStatusOptionFlags.RenamesFromRewrites; + } + + if (options.ExcludeSubmodules) + { + coreOptions.Flags |= + GitStatusOptionFlags.ExcludeSubmodules; + } + + if (options.RecurseIgnoredDirs) + { + coreOptions.Flags |= + 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 StatusEntry StateChanged(IntPtr filePathPtr, uint state) + private unsafe void AddStatusEntryForDelta(FileStatus gitStatus, git_diff_delta* deltaHeadToIndex, git_diff_delta* deltaIndexToWorkDir) { - var filePath = FilePathMarshaler.FromNative(filePathPtr); - var gitStatus = (FileStatus)state; + RenameDetails headToIndexRenameDetails = null; + RenameDetails indexToWorkDirRenameDetails = null; + + if ((gitStatus & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex) + { + headToIndexRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaHeadToIndex->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaHeadToIndex->new_file.Path), + (int)deltaHeadToIndex->similarity); + } + + if ((gitStatus & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) + { + indexToWorkDirRenameDetails = + new RenameDetails(LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->old_file.Path), + LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir->new_file.Path), + (int)deltaIndexToWorkDir->similarity); + } - foreach (KeyValuePair> kvp in dispatcher) + var filePath = LaxUtf8Marshaler.FromNative(deltaIndexToWorkDir != null ? + deltaIndexToWorkDir->new_file.Path : + deltaHeadToIndex->new_file.Path); + + StatusEntry statusEntry = new StatusEntry(filePath, gitStatus, headToIndexRenameDetails, indexToWorkDirRenameDetails); + + 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, filePath.Native); + kvp.Value(this, statusEntry); + } } - return new StatusEntry(filePath.Native, gitStatus); + statusEntries.Add(statusEntry); } /// - /// Gets the for the specified relative path. + /// Gets the for the specified relative path. /// - public virtual FileStatus this[string path] + public virtual StatusEntry this[string path] { get { @@ -87,89 +204,113 @@ public virtual FileStatus this[string path] if (entries.Count == 0) { - return FileStatus.Nonexistent; + return new StatusEntry(path, FileStatus.Nonexistent); } - return entries.Single().State; + return entries.Single(); } } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return statusEntries.GetEnumerator(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } /// - /// List of files added to the index, which are not in the current commit + /// List of files added to the index, which are not in the current commit /// - public virtual IEnumerable Added + public virtual IEnumerable Added { get { return added; } } /// - /// List of files added to the index, which are already in the current commit with different content + /// List of files added to the index, which are already in the current commit with different content /// - public virtual IEnumerable Staged + public virtual IEnumerable Staged { get { return staged; } } /// - /// List of files removed from the index but are existent in the current commit + /// List of files removed from the index but are existent in the current commit /// - public virtual IEnumerable Removed + public virtual IEnumerable Removed { get { return removed; } } /// - /// List of files existent in the index but are missing in the working directory + /// List of files existent in the index but are missing in the working directory /// - public virtual IEnumerable Missing + public virtual IEnumerable Missing { get { return missing; } } /// - /// List of files with unstaged modifications. A file may be modified and staged at the same time if it has been modified after adding. + /// List of files with unstaged modifications. A file may be modified and staged at the same time if it has been modified after adding. /// - public virtual IEnumerable Modified + public virtual IEnumerable Modified { get { return modified; } } /// - /// List of files existing in the working directory but are neither tracked in the index nor in the current commit. + /// List of files existing in the working directory but are neither tracked in the index nor in the current commit. /// - public virtual IEnumerable Untracked + public virtual IEnumerable Untracked { get { return untracked; } } /// - /// List of files existing in the working directory that are ignored. + /// List of files existing in the working directory that are ignored. /// - public virtual IEnumerable Ignored + public virtual IEnumerable Ignored { get { return ignored; } } /// - /// True if the index or the working directory has been altered since the last commit. False otherwise. + /// List of files that were renamed and staged. + /// + public virtual IEnumerable RenamedInIndex + { + get { return renamedInIndex; } + } + + /// + /// List of files that were renamed in the working directory but have not been staged. + /// + 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. /// public virtual bool IsDirty { @@ -180,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 new file mode 100644 index 000000000..f15028918 --- /dev/null +++ b/LibGit2Sharp/ResetMode.cs @@ -0,0 +1,26 @@ +namespace LibGit2Sharp +{ + /// + /// Specifies the kind of operation that should perform. + /// + public enum ResetMode + { + /// + /// Moves the branch pointed to by HEAD to the specified commit object. + /// + Soft = 1, + + /// + /// Moves the branch pointed to by HEAD to the specified commit object and resets the index + /// to the tree recorded by the commit. + /// + Mixed, + + /// + /// Moves the branch pointed to by HEAD to the specified commit object, resets the index + /// to the tree recorded by the commit and updates the working directory to match the content + /// of the index. + /// + Hard, + } +} diff --git a/LibGit2Sharp/ResetOptions.cs b/LibGit2Sharp/ResetOptions.cs deleted file mode 100644 index 66f009ed3..000000000 --- a/LibGit2Sharp/ResetOptions.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace LibGit2Sharp -{ - /// - /// Specifies the kind of operation that should perform. - /// - public enum ResetOptions - { - /// - /// Moves the branch pointed to by HEAD to the specified commit object. - /// - Soft = 1, - - /// - /// Moves the branch pointed to by HEAD to the specified commit object and resets the index - /// to the tree recorded by the commit. - /// - Mixed, - - /// - /// Moves the branch pointed to by HEAD to the specified commit object, resets the index - /// to the tree recorded by the commit and updates the working directory to match the content - /// of the index. - /// - Hard, - } -} diff --git a/LibGit2Sharp/RevertOptions.cs b/LibGit2Sharp/RevertOptions.cs new file mode 100644 index 000000000..882afb082 --- /dev/null +++ b/LibGit2Sharp/RevertOptions.cs @@ -0,0 +1,27 @@ +namespace LibGit2Sharp +{ + /// + /// Options controlling Revert behavior. + /// + 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() + { } + + /// + /// When reverting a merge commit, the parent number to consider as + /// mainline, starting from offset 1. + /// + /// As a merge commit has multiple parents, reverting 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). + /// + /// + public int Mainline { get; set; } + } +} diff --git a/LibGit2Sharp/RevertResult.cs b/LibGit2Sharp/RevertResult.cs new file mode 100644 index 000000000..8f9a270d3 --- /dev/null +++ b/LibGit2Sharp/RevertResult.cs @@ -0,0 +1,57 @@ +namespace LibGit2Sharp +{ + /// + /// Class to report the result of a revert. + /// + public class RevertResult + { + /// + /// Needed for mocking purposes. + /// + protected RevertResult() + { } + + internal RevertResult(RevertStatus status, Commit commit = null) + { + Commit = commit; + Status = status; + } + + /// + /// The resulting commit of the revert. + /// + /// This will return null if the revert was not committed. + /// This can happen if: + /// 1) The revert resulted in conflicts. + /// 2) The option to not commit on success is set. + /// + /// + public virtual Commit Commit { get; private set; } + + /// + /// The status of the revert. + /// + public virtual RevertStatus Status { get; private set; } + } + + /// + /// The status of what happened as a result of a revert. + /// + public enum RevertStatus + { + /// + /// The commit was successfully reverted. + /// + Reverted, + + /// + /// The revert resulted in conflicts. + /// + Conflicts, + + /// + /// Revert was run, but there were no changes to commit. + /// + NothingToRevert, + } +} diff --git a/LibGit2Sharp/RewriteHistoryOptions.cs b/LibGit2Sharp/RewriteHistoryOptions.cs new file mode 100644 index 000000000..59a982dc2 --- /dev/null +++ b/LibGit2Sharp/RewriteHistoryOptions.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace LibGit2Sharp +{ + /// + /// Options for a RewriteHistory operation. + /// + public sealed class RewriteHistoryOptions + { + /// + /// Initializes a new instance of the class. + /// + public RewriteHistoryOptions() + { + BackupRefsNamespace = "refs/original/"; + } + + /// + /// Namespace where rewritten references should be stored. + /// (required; default: "refs/original/") + /// + public string BackupRefsNamespace { get; set; } + + /// + /// Rewriter for commit metadata. + /// + public Func CommitHeaderRewriter { get; set; } + + /// + /// Rewriter for mangling parent links. + /// + public Func> CommitParentsRewriter { get; set; } + + /// + /// Rewriter for commit trees. + /// + public Func CommitTreeRewriter { get; set; } + + /// + /// Rewriter for tag names. This is called with + /// (OldTag.Name, OldTag.IsAnnotated, OldTarget.Identifier). + /// OldTarget.Identifier is either the SHA of a direct reference, + /// or the canonical name of a symbolic reference. + /// + public Func TagNameRewriter { get; set; } + + /// + /// Empty commits should be removed while rewriting. + /// + public bool PruneEmptyCommits { get; set; } + + /// + /// Action to exectute after rewrite succeeds, + /// but before it is finalized. + /// + /// An exception thrown here will rollback the operation. + /// This is useful to inspect the new state of the repository + /// and throw if you need to adjust and try again. + /// + /// + public Action OnSucceeding { get; set; } + + /// + /// Action to execute if an error occurred during rewrite, + /// before rollback of rewrite progress. + /// Does not fire for exceptions thrown in . + /// + /// This is useful to inspect the state of the repository + /// at the time of the exception for troubleshooting. + /// It is not meant to be used for general error handling; + /// for that use try/catch. + /// + /// + /// An exception thrown here will replace the original exception. + /// You may want to pass the callback exception as an innerException. + /// + /// + 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 c7d9bf762..7ed7a4916 100644 --- a/LibGit2Sharp/Signature.cs +++ b/LibGit2Sharp/Signature.cs @@ -1,14 +1,14 @@ using System; -using System.Runtime.InteropServices; +using System.Globalization; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// A signature + /// A signature /// - public class Signature : IEquatable + public sealed class Signature : IEquatable { private readonly DateTimeOffset when; private readonly string name; @@ -17,36 +17,52 @@ public 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 = new GitSignature(); - Marshal.PtrToStructure(signaturePtr, handle); - - name = Utf8Marshaler.FromNative(handle.Name); - email = Utf8Marshaler.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)); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The name. - /// The email. - /// The when. + /// The name. + /// The email. + /// The when. public Signature(string name, string email, DateTimeOffset when) { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + Ensure.ArgumentNotNullOrEmptyString(email, "email"); + Ensure.ArgumentDoesNotContainZeroByte(name, "name"); + Ensure.ArgumentDoesNotContainZeroByte(email, "email"); + this.name = name; this.email = email; 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); } /// - /// Gets the name. + /// Gets the name. /// public string Name { @@ -54,7 +70,7 @@ public string Name } /// - /// Gets the email. + /// Gets the email. /// public string Email { @@ -62,7 +78,7 @@ public string Email } /// - /// Gets the date when this signature happened. + /// Gets the date when this signature happened. /// public DateTimeOffset When { @@ -70,27 +86,27 @@ 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); } /// - /// 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 bool Equals(Signature other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -99,10 +115,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(Signature left, Signature right) { @@ -110,14 +126,42 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(Signature left, Signature right) { return !Equals(left, right); } + + /// + /// Returns " <>" for the current . + /// + /// The and of the current . + 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/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 new file mode 100644 index 000000000..4d2b0cd95 --- /dev/null +++ b/LibGit2Sharp/SimilarityOptions.cs @@ -0,0 +1,164 @@ +namespace LibGit2Sharp +{ + /// + /// Represents a mode for handling whitespace while detecting renames and copies. + /// + public enum WhitespaceMode + { + /// + /// Don't consider leading whitespace when comparing files + /// + IgnoreLeadingWhitespace, + + /// + /// Don't consider any whitespace when comparing files + /// + IgnoreAllWhitespace, + + /// + /// Include all whitespace when comparing files + /// + DontIgnoreWhitespace, + } + + /// + /// Represents a mode for detecting renames and copies. + /// + public enum RenameDetectionMode + { + /// + /// Obey the user's `diff.renames` configuration setting + /// + Default, + + /// + /// Attempt no rename or copy detection + /// + None, + + /// + /// Detect exact renames and copies (compare SHA hashes only) + /// + Exact, + + /// + /// Detect fuzzy renames (use similarity metric) + /// + Renames, + + /// + /// Detect renames and copies + /// + Copies, + + /// + /// Detect renames, and include unmodified files when looking for copies + /// + CopiesHarder, + } + + /// + /// Options for handling file similarity + /// + public sealed class SimilarityOptions + { + /// + /// Initializes a new instance of the class. + /// + public SimilarityOptions() + { + RenameDetectionMode = RenameDetectionMode.Default; + WhitespaceMode = WhitespaceMode.IgnoreLeadingWhitespace; + RenameThreshold = 50; + RenameFromRewriteThreshold = 50; + CopyThreshold = 50; + BreakRewriteThreshold = 60; + RenameLimit = 200; + } + + /// + /// Get a instance that does no rename detection + /// + public static SimilarityOptions None + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.None }; } + } + + /// + /// Get a instance that detects renames + /// + public static SimilarityOptions Renames + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Renames }; } + } + + /// + /// Get a instance that detects exact renames only + /// + public static SimilarityOptions Exact + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Exact }; } + } + + /// + /// Get a instance that detects renames and copies + /// + public static SimilarityOptions Copies + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Copies }; } + } + + /// + /// Get a instance that detects renames, and includes unmodified files when detecting copies + /// + public static SimilarityOptions CopiesHarder + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.CopiesHarder }; } + } + + /// + /// Get a instance that obeys the user's `diff.renames` setting + /// + public static SimilarityOptions Default + { + get { return new SimilarityOptions { RenameDetectionMode = RenameDetectionMode.Default }; } + } + + /// + /// The mode for detecting renames and copies + /// + public RenameDetectionMode RenameDetectionMode { get; set; } + + /// + /// The mode for handling whitespace when comparing files + /// + public WhitespaceMode WhitespaceMode { get; set; } + + /// + /// Similarity in order to consider a rename + /// + public int RenameThreshold { get; set; } + + /// + /// Similarity of a modified file in order to be eligible as a rename source + /// + public int RenameFromRewriteThreshold { get; set; } + + /// + /// Similarity to consider a file a copy + /// + public int CopyThreshold { get; set; } + + /// + /// Similarity to split modify into an add/delete pair + /// + public int BreakRewriteThreshold { get; set; } + + /// + /// Maximum similarity sources to examine for a file + /// + public int RenameLimit { get; set; } + + // TODO: custom metric + } +} diff --git a/LibGit2Sharp/SmartSubtransport.cs b/LibGit2Sharp/SmartSubtransport.cs new file mode 100644 index 000000000..6160c849b --- /dev/null +++ b/LibGit2Sharp/SmartSubtransport.cs @@ -0,0 +1,303 @@ +using System; +using System.Runtime.InteropServices; +using System.Text; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// An enumeration of the type of connections which a "smart" subtransport + /// may be asked to create on behalf of libgit2. + /// + public enum GitSmartSubtransportAction + { + /// + /// For HTTP, this indicates a GET to /info/refs?service=git-upload-pack + /// + UploadPackList = 1, + + /// + /// For HTTP, this indicates a POST to /git-upload-pack + /// + UploadPack = 2, + + /// + /// For HTTP, this indicates a GET to /info/refs?service=git-receive-pack + /// + ReceivePackList = 3, + + /// + /// For HTTP, this indicates a POST to /git-receive-pack + /// + ReceivePack = 4 + } + + /// + /// Base class for custom RPC-based subtransports that use the standard + /// "smart" git protocol. RPC-based subtransports perform work over + /// multiple requests, like the http transport. + /// + public abstract class RpcSmartSubtransport : SmartSubtransport + { + } + + /// + /// Base class for typical custom subtransports for the "smart" + /// transport that work over a single connection, like the git and ssh + /// transports. + /// + 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); + + /// + /// 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 + /// cleanup steps to your subclass. Be sure to call base.Dispose(). + /// + protected virtual void Dispose() + { + Close(); + + if (IntPtr.Zero != nativeSubtransportPointer) + { + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeSubtransportPointer, GitSmartSubtransport.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativeSubtransportPointer); + nativeSubtransportPointer = IntPtr.Zero; + } + } + + private IntPtr nativeSubtransportPointer; + + internal IntPtr GitSmartSubtransportPointer + { + get + { + if (IntPtr.Zero == nativeSubtransportPointer) + { + var nativeTransport = new GitSmartSubtransport(); + + nativeTransport.Action = EntryPoints.ActionCallback; + nativeTransport.Close = EntryPoints.CloseCallback; + nativeTransport.Free = EntryPoints.FreeCallback; + + nativeTransport.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativeSubtransportPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeTransport)); + Marshal.StructureToPtr(nativeTransport, nativeSubtransportPointer, false); + } + + return nativeSubtransportPointer; + } + } + + private static class EntryPoints + { + // Because our GitSmartSubtransport structure exists on the managed heap only for a short time (to be marshaled + // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time + // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. + // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. + public static GitSmartSubtransport.action_callback ActionCallback = new GitSmartSubtransport.action_callback(Action); + public static GitSmartSubtransport.close_callback CloseCallback = new GitSmartSubtransport.close_callback(Close); + public static GitSmartSubtransport.free_callback FreeCallback = new GitSmartSubtransport.free_callback(Free); + + private static int Action( + out IntPtr stream, + IntPtr subtransport, + IntPtr url, + GitSmartSubtransportAction action) + { + stream = IntPtr.Zero; + + SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; + string urlAsString = LaxUtf8Marshaler.FromNative(url); + + if (t == null) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "no subtransport provided"); + return (int)GitErrorCode.Error; + } + + if (string.IsNullOrEmpty(urlAsString)) + { + urlAsString = t.LastActionUrl; + } + + 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 (t == null) + { + Proxy.git_error_set_str(GitErrorCategory.Net, "no subtransport provided"); + 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) + { + SmartSubtransport t = GCHandle.FromIntPtr(Marshal.ReadIntPtr(subtransport, GitSmartSubtransport.GCHandleOffset)).Target as SmartSubtransport; + + if (null != t) + { + try + { + t.Dispose(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Net, ex); + } + } + } + } + } +} diff --git a/LibGit2Sharp/SmartSubtransportRegistration.cs b/LibGit2Sharp/SmartSubtransportRegistration.cs new file mode 100644 index 000000000..d33887122 --- /dev/null +++ b/LibGit2Sharp/SmartSubtransportRegistration.cs @@ -0,0 +1,105 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// An object representing the registration of a SmartSubtransport type with libgit2 + /// under a particular scheme (eg "http"). + /// + /// The type of SmartSubtransport to register + public sealed class SmartSubtransportRegistration : SmartSubtransportRegistrationData + where T : SmartSubtransport, new() + { + /// + /// Creates a new native registration for a smart protocol transport + /// in libgit2. + /// + /// The URL scheme (eg "http") to register + internal SmartSubtransportRegistration(string scheme) + { + Scheme = scheme; + RegistrationPointer = CreateRegistrationPointer(); + FunctionPointer = CreateFunctionPointer(); + } + + private IntPtr CreateRegistrationPointer() + { + var registration = new GitSmartSubtransportRegistration(); + + registration.SubtransportCallback = Marshal.GetFunctionPointerForDelegate(EntryPoints.SubtransportCallback); + registration.Rpc = typeof(RpcSmartSubtransport).IsAssignableFrom(typeof(T)) ? (uint)1 : (uint)0; + + var registrationPointer = Marshal.AllocHGlobal(Marshal.SizeOf(registration)); + Marshal.StructureToPtr(registration, registrationPointer, false); + + return registrationPointer; + } + + private IntPtr CreateFunctionPointer() + { + return Marshal.GetFunctionPointerForDelegate(EntryPoints.TransportCallback); + } + + internal void Free() + { + Marshal.FreeHGlobal(RegistrationPointer); + RegistrationPointer = IntPtr.Zero; + } + + private static class EntryPoints + { + // Because our GitSmartSubtransportRegistration structure exists on the managed heap only for a short time (to be marshaled + // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time + // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. + // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. + public static GitSmartSubtransportRegistration.create_callback SubtransportCallback = new GitSmartSubtransportRegistration.create_callback(Subtransport); + public static NativeMethods.git_transport_cb TransportCallback = new NativeMethods.git_transport_cb(Transport); + + private static int Subtransport( + out IntPtr subtransport, + IntPtr transport, + IntPtr payload) + { + subtransport = IntPtr.Zero; + + try + { + var obj = new T(); + obj.Transport = transport; + subtransport = obj.GitSmartSubtransportPointer; + + return 0; + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Net, ex); + } + + return (int)GitErrorCode.Error; + } + + private static int Transport( + out IntPtr transport, + IntPtr remote, + IntPtr payload) + { + transport = IntPtr.Zero; + + try + { + return NativeMethods.git_transport_smart(out transport, remote, payload); + } + catch (Exception 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 new file mode 100644 index 000000000..008d1fcd0 --- /dev/null +++ b/LibGit2Sharp/SmartSubtransportStream.cs @@ -0,0 +1,221 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A stream that represents a two-way connection (socket) for a SmartSubtransport. + /// + public abstract class SmartSubtransportStream + { + /// + /// This is to quiet the MetaFixture.TypesInLibGit2SharpMustBeExtensibleInATestingContext test. + /// Do not use this constructor. + /// + protected internal SmartSubtransportStream() + { + throw new InvalidOperationException(); + } + + /// + /// Base constructor for SmartTransportStream. Make sure that your derived class calls this base constructor. + /// + /// The subtransport that this stream represents a connection over. + protected SmartSubtransportStream(SmartSubtransport subtransport) + { + this.subtransport = subtransport; + } + + /// + /// Invoked by libgit2 when this stream is no longer needed. + /// Override this method to add additional cleanup steps to your subclass. Be sure + /// to call base.Free(). + /// + protected virtual void Free() + { + if (IntPtr.Zero != nativeStreamPointer) + { + GCHandle.FromIntPtr(Marshal.ReadIntPtr(nativeStreamPointer, GitSmartSubtransportStream.GCHandleOffset)).Free(); + Marshal.FreeHGlobal(nativeStreamPointer); + nativeStreamPointer = IntPtr.Zero; + } + } + + /// + /// Reads from the transport into the provided object. + /// + /// 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); + + /// + /// Writes the content of a given stream to the transport. + /// + /// 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; } + } + + private Exception StoredError { get; set; } + + internal void SetError(Exception ex) + { + StoredError = ex; + } + + private SmartSubtransport subtransport; + private IntPtr nativeStreamPointer; + + internal IntPtr GitSmartTransportStreamPointer + { + get + { + if (IntPtr.Zero == nativeStreamPointer) + { + var nativeTransportStream = new GitSmartSubtransportStream(); + + nativeTransportStream.SmartTransport = this.subtransport.GitSmartSubtransportPointer; + nativeTransportStream.Read = EntryPoints.ReadCallback; + nativeTransportStream.Write = EntryPoints.WriteCallback; + nativeTransportStream.Free = EntryPoints.FreeCallback; + + nativeTransportStream.GCHandle = GCHandle.ToIntPtr(GCHandle.Alloc(this)); + nativeStreamPointer = Marshal.AllocHGlobal(Marshal.SizeOf(nativeTransportStream)); + Marshal.StructureToPtr(nativeTransportStream, nativeStreamPointer, false); + } + + return nativeStreamPointer; + } + } + + private static class EntryPoints + { + // Because our GitSmartSubtransportStream structure exists on the managed heap only for a short time (to be marshaled + // to native memory with StructureToPtr), we need to bind to static delegates. If at construction time + // we were to bind to the methods directly, that's the same as newing up a fresh delegate every time. + // Those delegates won't be rooted in the object graph and can be collected as soon as StructureToPtr finishes. + public static GitSmartSubtransportStream.read_callback ReadCallback = new GitSmartSubtransportStream.read_callback(Read); + 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, + UIntPtr buf_size, + out UIntPtr bytes_read) + { + bytes_read = UIntPtr.Zero; + + 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; + } + + try + { + using (UnmanagedMemoryStream memoryStream = new UnmanagedMemoryStream((byte*)buffer, 0, + (long)buf_size.ToUInt64(), + FileAccess.ReadWrite)) + { + long longBytesRead; + + int toReturn = transportStream.Read(memoryStream, (long)buf_size.ToUInt64(), out longBytesRead); + + bytes_read = new UIntPtr((ulong)Math.Max(0, longBytesRead)); + + return toReturn; + } + } + catch (Exception ex) + { + return SetError(transportStream, ex); + } + } + + private static unsafe int Write(IntPtr stream, IntPtr buffer, UIntPtr len) + { + 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 (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)) + { + return transportStream.Write(dataStream, length); + } + } + catch (Exception ex) + { + return SetError(transportStream, ex); + } + } + + private static void Free(IntPtr stream) + { + SmartSubtransportStream transportStream = + GCHandle.FromIntPtr(Marshal.ReadIntPtr(stream, GitSmartSubtransportStream.GCHandleOffset)).Target as SmartSubtransportStream; + + if (transportStream != null) + { + try + { + transportStream.Free(); + } + catch (Exception ex) + { + Proxy.git_error_set_str(GitErrorCategory.Net, ex); + } + } + } + } + } +} diff --git a/LibGit2Sharp/StageLevel.cs b/LibGit2Sharp/StageLevel.cs index c6a9b1b1f..b46d218a5 100644 --- a/LibGit2Sharp/StageLevel.cs +++ b/LibGit2Sharp/StageLevel.cs @@ -1,27 +1,27 @@ namespace LibGit2Sharp { /// - /// Disambiguates the different versions of an index entry during a merge. + /// Disambiguates the different versions of an index entry during a merge. /// public enum StageLevel { /// - /// The standard fully merged state for an index entry. + /// The standard fully merged state for an index entry. /// Staged = 0, /// - /// Version of the entry as it was in the common base merge commit. + /// Version of the entry as it was in the common base merge commit. /// Ancestor = 1, /// - /// Version of the entry as it is in the commit of the Head. + /// Version of the entry as it is in the commit of the Head. /// Ours = 2, /// - /// Version of the entry as it is in the commit being merged. + /// Version of the entry as it is in the commit being merged. /// Theirs = 3, } diff --git a/LibGit2Sharp/StageOptions.cs b/LibGit2Sharp/StageOptions.cs new file mode 100644 index 000000000..4e5d72608 --- /dev/null +++ b/LibGit2Sharp/StageOptions.cs @@ -0,0 +1,20 @@ +namespace LibGit2Sharp +{ + /// + /// Options to define file staging behavior. + /// + public sealed class StageOptions + { + /// + /// Stage ignored files. (Default = false) + /// + public bool IncludeIgnored { get; set; } + + /// + /// If set, the passed paths will be treated as explicit paths. + /// Use these options to determine how unmatched explicit paths + /// should be handled. (Default = null) + /// + public ExplicitPathsOptions ExplicitPathsOptions { get; set; } + } +} diff --git a/LibGit2Sharp/Stash.cs b/LibGit2Sharp/Stash.cs index e31c24b57..07bd6559c 100644 --- a/LibGit2Sharp/Stash.cs +++ b/LibGit2Sharp/Stash.cs @@ -1,37 +1,76 @@ -namespace LibGit2Sharp +using System.Globalization; +using System.Linq; + +namespace LibGit2Sharp { /// - /// A Stash - /// A stash is a snapshot of the dirty state of the working directory (i.e. the modified tracked files and staged changes) + /// A Stash + /// A stash is a snapshot of the dirty state of the working directory (i.e. the modified tracked files and staged changes) /// public class Stash : ReferenceWrapper { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Stash() { } internal Stash(Repository repo, ObjectId targetId, int index) - : base(repo, new DirectReference(string.Format("stash@{{{0}}}", index), repo, targetId), r => r.CanonicalName) + : base(repo, new DirectReference(string.Format(CultureInfo.InvariantCulture, "stash@{{{0}}}", index), repo, targetId), r => r.CanonicalName) { } /// - /// Gets the that this stash points to. + /// Gets the that contains to the captured content of the worktree when the + /// stash was created. /// - public virtual Commit Target + public virtual Commit WorkTree { get { return TargetObject; } } /// - /// Gets the message associated to this . + /// Gets the base (i.e. the HEAD when the stash was + /// created). + /// + public virtual Commit Base + { + get { return TargetObject.Parents.First(); } + } + + /// + /// Gets the that contains the captured content of the index when the stash was + /// created. + /// + public virtual Commit Index + { + get { return GetParentAtOrDefault(1); } + } + + /// + /// Gets the that contains the list of either the untracked files, the ignored files, or both, + /// depending on the options passed when the stash was created. + /// + public virtual Commit Untracked + { + get { return GetParentAtOrDefault(2); } + } + + private Commit GetParentAtOrDefault(int parentIndex) + { + return TargetObject.Parents.ElementAtOrDefault(parentIndex); + } + + /// + /// Gets the message associated to this . /// public virtual string Message { - get { return Target.Message; } + get { return WorkTree.Message; } } + /// + /// Returns "stash@{i}", where i is the index of this . + /// protected override string Shorten() { return CanonicalName; 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 54078db40..42162ada5 100644 --- a/LibGit2Sharp/StashCollection.cs +++ b/LibGit2Sharp/StashCollection.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -8,7 +9,7 @@ namespace LibGit2Sharp { /// - /// The collection of es in a + /// The collection of es in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class StashCollection : IEnumerable @@ -16,15 +17,15 @@ public class StashCollection : IEnumerable internal readonly Repository repo; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected StashCollection() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repo. + /// The repo. internal StashCollection(Repository repo) { this.repo = repo; @@ -33,19 +34,23 @@ internal StashCollection(Repository repo) #region Implementation of IEnumerable /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. + /// + /// The enumerator returns the stashes by descending order (last stash is returned first). + /// /// - /// An object that can be used to iterate through the collection. - public IEnumerator GetEnumerator() + /// 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(); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -54,17 +59,72 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Creates a stash with the specified message. + /// Gets the corresponding to the specified index (0 being the most recent one). + /// + public virtual Stash this[int index] + { + get + { + if (index < 0) + { + 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); + + 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. /// /// The of the user who stashes - /// The message of the stash. - /// A combination of flags + /// The message of the stash. + /// A combination of flags /// the newly created - public virtual Stash Add(Signature stasher, string message = null, StashOptions options = StashOptions.Default) + public virtual Stash Add(Signature stasher, string message, StashModifiers options) { Ensure.ArgumentNotNull(stasher, "stasher"); - string prettifiedMessage = Proxy.git_message_prettify(string.IsNullOrEmpty(message) ? string.Empty : message); + string prettifiedMessage = Proxy.git_message_prettify(string.IsNullOrEmpty(message) ? string.Empty : message, null); ObjectId oid = Proxy.git_stash_save(repo.Handle, stasher, prettifiedMessage, options); @@ -77,12 +137,111 @@ public virtual Stash Add(Signature stasher, string message = null, StashOptions 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. + /// + /// The index of the stash to remove (0 being the most recent one). + public virtual void Remove(int index) + { + if (index < 0) + { + throw new ArgumentException("The passed index must be a positive integer.", nameof(index)); + } + + Proxy.git_stash_drop(repo.Handle, index); + } + 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/StashOptions.cs b/LibGit2Sharp/StashModifiers.cs similarity index 96% rename from LibGit2Sharp/StashOptions.cs rename to LibGit2Sharp/StashModifiers.cs index 9933b27f2..b0e6d41ff 100644 --- a/LibGit2Sharp/StashOptions.cs +++ b/LibGit2Sharp/StashModifiers.cs @@ -6,7 +6,7 @@ namespace LibGit2Sharp /// Options controlling Stash behavior. /// [Flags] - public enum StashOptions + public enum StashModifiers { /// /// Default diff --git a/LibGit2Sharp/StatusEntry.cs b/LibGit2Sharp/StatusEntry.cs index 9fad0fb87..bd2ef8883 100644 --- a/LibGit2Sharp/StatusEntry.cs +++ b/LibGit2Sharp/StatusEntry.cs @@ -1,35 +1,40 @@ using System; using System.Diagnostics; +using System.Globalization; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Holds the calculated status of a particular file at a particular instant. + /// Holds the calculated status of a particular file at a particular instant. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class StatusEntry : IEquatable { private readonly string filePath; private readonly FileStatus state; + private readonly RenameDetails headToIndexRenameDetails; + private readonly RenameDetails indexToWorkDirRenameDetails; private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.FilePath, x => x.State); + new LambdaEqualityHelper(x => x.FilePath, x => x.State, x => x.HeadToIndexRenameDetails, x => x.IndexToWorkDirRenameDetails); /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected StatusEntry() { } - internal StatusEntry(string filePath, FileStatus state) + internal StatusEntry(string filePath, FileStatus state, RenameDetails headToIndexRenameDetails = null, RenameDetails indexToWorkDirRenameDetails = null) { this.filePath = filePath; this.state = state; + this.headToIndexRenameDetails = headToIndexRenameDetails; + this.indexToWorkDirRenameDetails = indexToWorkDirRenameDetails; } /// - /// Gets the of the file. + /// Gets the of the file. /// public virtual FileStatus State { @@ -37,7 +42,7 @@ public virtual FileStatus State } /// - /// Gets the relative filepath to the working directory of the file. + /// Gets the relative new filepath to the working directory of the file. /// public virtual string FilePath { @@ -45,27 +50,43 @@ public virtual string FilePath } /// - /// Determines whether the specified is equal to the current . + /// Gets the rename details from the HEAD to the Index, if this contains /// - /// The to compare with the current . - /// True if the specified is equal to the current ; otherwise, false. + public virtual RenameDetails HeadToIndexRenameDetails + { + get { return headToIndexRenameDetails; } + } + + /// + /// Gets the rename details from the Index to the working directory, if this contains + /// + public virtual RenameDetails IndexToWorkDirRenameDetails + { + get { return indexToWorkDirRenameDetails; } + } + + /// + /// 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 StatusEntry); } /// - /// 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 bool Equals(StatusEntry other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -74,10 +95,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(StatusEntry left, StatusEntry right) { @@ -85,10 +106,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(StatusEntry left, StatusEntry right) { @@ -97,7 +118,20 @@ public override int GetHashCode() private string DebuggerDisplay { - get { return string.Format("{0}: {1}", State, FilePath); } + get + { + if ((State & FileStatus.RenamedInIndex) == FileStatus.RenamedInIndex || + (State & FileStatus.RenamedInWorkdir) == FileStatus.RenamedInWorkdir) + { + string oldFilePath = ((State & FileStatus.RenamedInIndex) != 0) + ? HeadToIndexRenameDetails.OldFilePath + : IndexToWorkDirRenameDetails.OldFilePath; + + return string.Format(CultureInfo.InvariantCulture, "{0}: {1} -> {2}", State, oldFilePath, FilePath); + } + + return string.Format(CultureInfo.InvariantCulture, "{0}: {1}", State, FilePath); + } } } } diff --git a/LibGit2Sharp/StatusOptions.cs b/LibGit2Sharp/StatusOptions.cs new file mode 100644 index 000000000..47dba9197 --- /dev/null +++ b/LibGit2Sharp/StatusOptions.cs @@ -0,0 +1,109 @@ +namespace LibGit2Sharp +{ + /// + /// Flags controlling what files are reported by status. + /// + public enum StatusShowOption + { + /// + /// Both the index and working directory are examined for changes + /// + IndexAndWorkDir = 0, + + /// + /// Only the index is examined for changes + /// + IndexOnly = 1, + + /// + /// Only the working directory is examined for changes + /// + WorkDirOnly = 2 + } + + /// + /// Options controlling the status behavior. + /// + public sealed class StatusOptions + { + /// + /// Initializes a new instance of the class. + /// By default, both the index and the working directory will be scanned + /// for status, and renames will be detected from changes staged in the + /// index only. + /// + public StatusOptions() + { + DetectRenamesInIndex = true; + IncludeIgnored = true; + IncludeUntracked = true; + RecurseUntrackedDirs = true; + } + + /// + /// Which files should be scanned and returned + /// + public StatusShowOption Show { get; set; } + + /// + /// Examine the staged changes for renames. + /// + public bool DetectRenamesInIndex { get; set; } + + /// + /// Examine unstaged changes in the working directory for renames. + /// + public bool DetectRenamesInWorkDir { get; set; } + + /// + /// Exclude submodules from being scanned for status + /// + public bool ExcludeSubmodules { get; set; } + + /// + /// 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 new file mode 100644 index 000000000..f8193af13 --- /dev/null +++ b/LibGit2Sharp/Submodule.cs @@ -0,0 +1,157 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// A Submodule. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class Submodule : IEquatable, IBelongToARepository + { + private static readonly LambdaEqualityHelper equalityHelper = + new LambdaEqualityHelper(x => x.Name, x => x.HeadCommitId); + + private readonly Repository repo; + private readonly string name; + private readonly string path; + private readonly string url; + private readonly ILazy headCommitId; + private readonly ILazy indexCommitId; + private readonly ILazy workdirCommitId; + private readonly ILazy fetchRecurseSubmodulesRule; + private readonly ILazy ignoreRule; + private readonly ILazy updateRule; + + /// + /// Needed for mocking purposes. + /// + protected Submodule() + { } + + internal Submodule(Repository repo, string name, string path, string url) + { + this.repo = repo; + this.name = name; + this.path = path; + this.url = url; + + var commitIds = new SubmoduleLazyGroup(repo, name); + headCommitId = commitIds.AddLazy(Proxy.git_submodule_head_id); + indexCommitId = commitIds.AddLazy(Proxy.git_submodule_index_id); + workdirCommitId = commitIds.AddLazy(Proxy.git_submodule_wd_id); + + 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_strategy); + } + + /// + /// The name of the submodule. + /// + public virtual string Name { get { return name; } } + + /// + /// The path of the submodule. + /// + public virtual string Path { get { return path; } } + + /// + /// The URL of the submodule. + /// + public virtual string Url { get { return url; } } + + /// + /// The commit ID for this submodule in the current HEAD tree. + /// + public virtual ObjectId HeadCommitId { get { return headCommitId.Value; } } + + /// + /// The commit ID for this submodule in the index. + /// + public virtual ObjectId IndexCommitId { get { return indexCommitId.Value; } } + + /// + /// The commit ID for this submodule in the current working directory. + /// + public virtual ObjectId WorkDirCommitId { get { return workdirCommitId.Value; } } + + /// + /// The fetchRecurseSubmodules rule for the submodule. + /// + /// Note that at this time, LibGit2Sharp does not honor this setting and the + /// fetch functionality current ignores submodules. + /// + public virtual SubmoduleRecurse FetchRecurseSubmodulesRule { get { return fetchRecurseSubmodulesRule.Value; } } + + /// + /// The ignore rule of the submodule. + /// + public virtual SubmoduleIgnore IgnoreRule { get { return ignoreRule.Value; } } + + /// + /// The update rule of the submodule. + /// + 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 commit. + /// + /// The of this submodule. + public virtual SubmoduleStatus RetrieveStatus() + { + return Proxy.git_submodule_status(repo.Handle, Name); + } + + /// + /// 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 Submodule); + } + + /// + /// 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(Submodule 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); + } + + /// + /// 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, Url); + } + } + + IRepository IBelongToARepository.Repository { get { return repo; } } + } +} diff --git a/LibGit2Sharp/SubmoduleCollection.cs b/LibGit2Sharp/SubmoduleCollection.cs new file mode 100644 index 000000000..061196c7d --- /dev/null +++ b/LibGit2Sharp/SubmoduleCollection.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using LibGit2Sharp.Core; +using LibGit2Sharp.Core.Handles; + +namespace LibGit2Sharp +{ + /// + /// The collection of submodules in a + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class SubmoduleCollection : IEnumerable + { + internal readonly Repository repo; + + /// + /// Needed for mocking purposes. + /// + protected SubmoduleCollection() + { } + + /// + /// Initializes a new instance of the class. + /// + /// The repo. + internal SubmoduleCollection(Repository repo) + { + this.repo = repo; + } + + /// + /// Gets the with the specified name. + /// + public virtual Submodule this[string name] + { + get + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + 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. + /// + /// An object that can be used to iterate through the collection. + public virtual IEnumerator GetEnumerator() + { + return Proxy.git_submodule_foreach(repo.Handle, (h, n) => LaxUtf8Marshaler.FromNative(n)) + .Select(n => this[n]) + .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(); + } + + 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; + }); + } + + internal T Lookup(string name, Func selector, bool throwIfNotFound = false) + { + using (var handle = Proxy.git_submodule_lookup(repo.Handle, name)) + { + if (handle != null) + { + Proxy.git_submodule_reload(handle); + return selector(handle); + } + + if (throwIfNotFound) + { + throw new LibGit2SharpException("Submodule lookup failed for '{0}'.", name); + } + + return default(T); + } + } + + private string DebuggerDisplay + { + get + { + return string.Format(CultureInfo.InvariantCulture, "Count = {0}", this.Count()); + } + } + } +} diff --git a/LibGit2Sharp/SubmoduleExtensions.cs b/LibGit2Sharp/SubmoduleExtensions.cs new file mode 100644 index 000000000..377067b24 --- /dev/null +++ b/LibGit2Sharp/SubmoduleExtensions.cs @@ -0,0 +1,27 @@ +namespace LibGit2Sharp +{ + /// + /// Extensions related to submodules + /// + public static class SubmoduleExtensions + { + private const SubmoduleStatus UnmodifiedMask = ~(SubmoduleStatus.InConfig | SubmoduleStatus.InHead | SubmoduleStatus.InIndex | SubmoduleStatus.InWorkDir); + private const SubmoduleStatus WorkDirDirtyMask = SubmoduleStatus.WorkDirFilesIndexDirty | SubmoduleStatus.WorkDirFilesModified | SubmoduleStatus.WorkDirFilesUntracked; + + /// + /// The submodule is unmodified. + /// + public static bool IsUnmodified(this SubmoduleStatus @this) + { + return (@this & UnmodifiedMask) == SubmoduleStatus.Unmodified; + } + + /// + /// The submodule working directory is dirty. + /// + public static bool IsWorkingDirectoryDirty(this SubmoduleStatus @this) + { + return (@this & WorkDirDirtyMask) != SubmoduleStatus.Unmodified; + } + } +} diff --git a/LibGit2Sharp/SubmoduleIgnore.cs b/LibGit2Sharp/SubmoduleIgnore.cs new file mode 100644 index 000000000..e98407295 --- /dev/null +++ b/LibGit2Sharp/SubmoduleIgnore.cs @@ -0,0 +1,34 @@ +namespace LibGit2Sharp +{ + /// + /// Values that could be specified for how closely to examine the + /// working directory when getting submodule status. + /// + public enum SubmoduleIgnore + { + /// + /// Reset to the last saved ignore rule. + /// + Reset = -1, + + /// + /// Any change or untracked == dirty + /// + None = 1, + + /// + /// Dirty if tracked files change + /// + Untracked = 2, + + /// + /// Only dirty if HEAD moved + /// + Dirty = 3, + + /// + /// Never dirty + /// + All = 4, + } +} 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/SubmoduleStatus.cs b/LibGit2Sharp/SubmoduleStatus.cs new file mode 100644 index 000000000..19bee10bb --- /dev/null +++ b/LibGit2Sharp/SubmoduleStatus.cs @@ -0,0 +1,90 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Calculated status of a submodule in the working directory considering the current and the . + /// + [Flags] + public enum SubmoduleStatus + { + /// + /// No submodule changes detected. + /// + Unmodified = 0, + + /// + /// Superproject head contains submodule. + /// + /// Can be returned even if ignore is set to "ALL". + InHead = (1 << 0), + /// + /// Superproject index contains submodule. + /// + /// Can be returned even if ignore is set to "ALL". + InIndex = (1 << 1), + /// + /// Superproject gitmodules has submodule. + /// + /// Can be returned even if ignore is set to "ALL". + InConfig = (1 << 2), + /// + /// Superproject working directory has submodule. + /// + /// Can be returned even if ignore is set to "ALL". + InWorkDir = (1 << 3), + + /// + /// Submodule is in index, but not in head. + /// + /// Can be returned unless ignore is set to "ALL". + IndexAdded = (1 << 4), + /// + /// Submodule is in head, but not in index. + /// + /// Can be returned unless ignore is set to "ALL". + IndexDeleted = (1 << 5), + /// + /// Submodule in index and head don't match. + /// + /// Can be returned unless ignore is set to "ALL". + IndexModified = (1 << 6), + /// + /// Submodule in working directory is not initialized. + /// + /// Can be returned unless ignore is set to "ALL". + WorkDirUninitialized = (1 << 7), + /// + /// Submodule is in working directory, but not index. + /// + /// Can be returned unless ignore is set to "ALL". + WorkDirAdded = (1 << 8), + /// + /// Submodule is in index, but not working directory. + /// + /// Can be returned unless ignore is set to "ALL". + WorkDirDeleted = (1 << 9), + /// + /// Submodule in index and working directory head don't match. + /// + /// Can be returned unless ignore is set to "ALL". + WorkDirModified = (1 << 10), + + /// + /// Submodule working directory index is dirty. + /// + /// Can only be returned if ignore is "NONE" or "UNTRACKED". + WorkDirFilesIndexDirty = (1 << 11), + /// + /// Submodule working directory has modified files. + /// + /// Can only be returned if ignore is "NONE" or "UNTRACKED". + WorkDirFilesModified = (1 << 12), + + /// + /// Working directory contains untracked files. + /// + /// Can only be returned if ignore is "NONE". + WorkDirFilesUntracked = (1 << 13), + } +} diff --git a/LibGit2Sharp/SubmoduleUpdate.cs b/LibGit2Sharp/SubmoduleUpdate.cs new file mode 100644 index 000000000..45fad71d8 --- /dev/null +++ b/LibGit2Sharp/SubmoduleUpdate.cs @@ -0,0 +1,34 @@ +namespace LibGit2Sharp +{ + /// + /// Submodule update rule options. + /// + public enum SubmoduleUpdate + { + /// + /// Reset to the last saved update rule. + /// + Reset = -1, + /// + /// Only used when you don't want to specify any particular update + /// rule. + /// + 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 = 2, + /// + /// Merge the commit recorded in the superproject into the current branch of the submodule. + /// + Merge = 3, + /// + /// Do not update the submodule. + /// + 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/SupportedCredentialTypes.cs b/LibGit2Sharp/SupportedCredentialTypes.cs new file mode 100644 index 000000000..bc38a259e --- /dev/null +++ b/LibGit2Sharp/SupportedCredentialTypes.cs @@ -0,0 +1,22 @@ +using System; + +namespace LibGit2Sharp +{ + /// + /// Credential types supported by the server. If the server supports a particular type of + /// authentication, it will be set to true. + /// + [Flags] + public enum SupportedCredentialTypes + { + /// + /// Plain username and password combination + /// + UsernamePassword = (1 << 0), + + /// + /// Ask Windows to provide its default credentials for the current user (e.g. NTLM) + /// + Default = (1 << 1), + } +} diff --git a/LibGit2Sharp/SymbolicReference.cs b/LibGit2Sharp/SymbolicReference.cs index d98a92ed1..4615389e7 100644 --- a/LibGit2Sharp/SymbolicReference.cs +++ b/LibGit2Sharp/SymbolicReference.cs @@ -4,7 +4,7 @@ namespace LibGit2Sharp { /// - /// A SymbolicReference is a reference that points to another reference + /// A SymbolicReference is a reference that points to another reference /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class SymbolicReference : Reference @@ -12,19 +12,19 @@ public class SymbolicReference : Reference private readonly Reference target; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected SymbolicReference() { } - internal SymbolicReference(string canonicalName, string targetIdentifier, Reference target) - : base(canonicalName, targetIdentifier) + internal SymbolicReference(IRepository repo, string canonicalName, string targetIdentifier, Reference target) + : base(repo, canonicalName, targetIdentifier) { this.target = target; } /// - /// Gets the target of this + /// Gets the target of this /// public virtual Reference Target { @@ -32,9 +32,9 @@ public virtual Reference Target } /// - /// Recursively peels the target of the reference until a direct reference is encountered. + /// Recursively peels the target of the reference until a direct reference is encountered. /// - /// The this points to. + /// The this points to. public override DirectReference ResolveToDirectReference() { return (Target == null) ? null : Target.ResolveToDirectReference(); @@ -44,12 +44,13 @@ private string DebuggerDisplay { get { - var directReference = ResolveToDirectReference(); - return string.Format(CultureInfo.InvariantCulture, - "{0} => {1} => \"{2}\"", - CanonicalName, TargetIdentifier, - (directReference != null) ? directReference.TargetIdentifier : "?"); + "{0} => {1} => \"{2}\"", + CanonicalName, + TargetIdentifier, + (Target != null) + ? Target.TargetIdentifier + : "?"); } } } diff --git a/LibGit2Sharp/Tag.cs b/LibGit2Sharp/Tag.cs index 4881b4963..cb2436346 100644 --- a/LibGit2Sharp/Tag.cs +++ b/LibGit2Sharp/Tag.cs @@ -1,24 +1,23 @@ namespace LibGit2Sharp { /// - /// A Tag + /// A Tag /// public class Tag : ReferenceWrapper { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected Tag() { } internal Tag(Repository repo, Reference reference, string canonicalName) : base(repo, reference, _ => canonicalName) - { - } + { } /// - /// Gets the optional information associated to this tag. - /// When the is a lightweight tag, null is returned. + /// Gets the optional information associated to this tag. + /// When the is a lightweight tag, null is returned. /// public virtual TagAnnotation Annotation { @@ -26,7 +25,7 @@ public virtual TagAnnotation Annotation } /// - /// Gets the that this tag points to. + /// Gets the that this tag points to. /// public virtual GitObject Target { @@ -41,7 +40,28 @@ public virtual GitObject Target } /// - /// Indicates whether the tag holds any metadata. + /// 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. /// public virtual bool IsAnnotated { @@ -49,13 +69,13 @@ public virtual bool IsAnnotated } /// - /// Removes redundent leading namespaces (regarding the kind of - /// reference being wrapped) from the canonical name. + /// Removes redundent leading namespaces (regarding the kind of + /// reference being wrapped) from the canonical name. /// /// The friendly shortened name protected override string Shorten() { - return CanonicalName.Substring("refs/tags/".Length); + return CanonicalName.Substring(Reference.TagPrefix.Length); } } } diff --git a/LibGit2Sharp/TagAnnotation.cs b/LibGit2Sharp/TagAnnotation.cs index 7e3044643..66a84d556 100644 --- a/LibGit2Sharp/TagAnnotation.cs +++ b/LibGit2Sharp/TagAnnotation.cs @@ -3,7 +3,7 @@ namespace LibGit2Sharp { /// - /// A TagAnnotation + /// A TagAnnotation /// public class TagAnnotation : GitObject { @@ -14,7 +14,7 @@ public class TagAnnotation : GitObject private readonly ILazy lazyTagger; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected TagAnnotation() { } @@ -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_oid(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); @@ -32,22 +36,22 @@ internal TagAnnotation(Repository repo, ObjectId id) } /// - /// Gets the name of this tag. + /// Gets the name of this tag. /// public virtual string Name { get { return lazyName.Value; } } /// - /// Gets the message of this tag. + /// Gets the message of this tag. /// public virtual string Message { get { return lazyMessage.Value; } } /// - /// Gets the that this tag annotation points to. + /// Gets the that this tag annotation points to. /// public virtual GitObject Target { get { return lazyTarget.Value; } } /// - /// Gets the tagger. + /// Gets the tagger. /// public virtual Signature Tagger { get { return lazyTagger.Value; } } } diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs index aac77a28f..98bfd257d 100644 --- a/LibGit2Sharp/TagCollection.cs +++ b/LibGit2Sharp/TagCollection.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -9,31 +8,30 @@ namespace LibGit2Sharp { /// - /// The collection of s in a + /// The collection of s in a /// [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TagCollection : IEnumerable { internal readonly Repository repo; - private const string refsTagsPrefix = "refs/tags/"; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected TagCollection() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - /// The repo. + /// The repo. internal TagCollection(Repository repo) { this.repo = repo; } /// - /// Gets the with the specified name. + /// Gets the with the specified name. /// public virtual Tag this[string name] { @@ -49,9 +47,9 @@ public virtual Tag this[string name] #region IEnumerable Members /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. public virtual IEnumerator GetEnumerator() { return Proxy @@ -61,9 +59,9 @@ public virtual IEnumerator GetEnumerator() } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -72,22 +70,89 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Creates an annotated tag with the specified name. + /// Creates an annotated tag with the specified name. /// - /// The name. - /// The target . - /// The tagger. - /// The message. - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// - public virtual Tag Add(string name, GitObject target, Signature tagger, string message, bool allowOverwrite = false) + /// 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. + /// + /// The name. + /// The target . + /// The tagger. + /// 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) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(target, "target"); Ensure.ArgumentNotNull(tagger, "tagger"); Ensure.ArgumentNotNull(message, "message"); - string prettifiedMessage = Proxy.git_message_prettify(message); + string prettifiedMessage = Proxy.git_message_prettify(message, null); Proxy.git_tag_create(repo.Handle, name, target, tagger, prettifiedMessage, allowOverwrite); @@ -95,28 +160,24 @@ public virtual Tag Add(string name, GitObject target, Signature tagger, string m } /// - /// Creates an annotated tag with the specified name. + /// Creates a lightweight tag with the specified name. /// - /// The name. - /// The target which can be sha or a canonical reference name. - /// The tagger. - /// The message. - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Tag Create(string name, string target, Signature tagger, string message, bool allowOverwrite = false) + /// The name. + /// The target . + /// The added . + public virtual Tag Add(string name, GitObject target) { - return this.Add(name, target, tagger, message, allowOverwrite); + return Add(name, target, false); } /// - /// Creates a lightweight tag with the specified name. + /// Creates a lightweight tag with the specified name. /// - /// The name. - /// The target . - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// - public virtual Tag Add(string name, GitObject target, bool allowOverwrite = false) + /// The name. + /// 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) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNull(target, "target"); @@ -127,69 +188,56 @@ public virtual Tag Add(string name, GitObject target, bool allowOverwrite = fals } /// - /// Creates a lightweight tag with the specified name. + /// Deletes the tag with the specified name. /// - /// The name. - /// The target which can be sha or a canonical reference name. - /// True to allow silent overwriting a potentially existing tag, false otherwise. - /// - [Obsolete("This method will be removed in the next release. Please use Add() instead.")] - public virtual Tag Create(string name, string target, bool allowOverwrite = false) + /// The short or canonical name of the tag to delete. + public virtual void Remove(string name) { - return this.Add(name, target, allowOverwrite); + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + + Proxy.git_tag_delete(repo.Handle, UnCanonicalizeName(name)); } /// - /// Deletes the tag with the specified name. + /// Deletes the tag with the specified name. /// - /// The tag to delete. + /// The tag to delete. public virtual void Remove(Tag tag) { Ensure.ArgumentNotNull(tag, "tag"); - this.Remove(tag.CanonicalName); - } - - /// - /// Deletes the tag with the specified name. - /// - /// The short or canonical name of the tag to delete. - [Obsolete("This method will be removed in the next release. Please use Remove() instead.")] - public virtual void Delete(string name) - { - this.Remove(name); + Remove(tag.CanonicalName); } private static string NormalizeToCanonicalName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - if (name.StartsWith(refsTagsPrefix, StringComparison.Ordinal)) + if (name.LooksLikeTag()) { return name; } - return string.Concat(refsTagsPrefix, name); + return string.Concat(Reference.TagPrefix, name); } - internal static string UnCanonicalizeName(string name) + private static string UnCanonicalizeName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); - if (!name.StartsWith(refsTagsPrefix, StringComparison.Ordinal)) + if (!name.LooksLikeTag()) { return name; } - return name.Substring(refsTagsPrefix.Length); + return name.Substring(Reference.TagPrefix.Length); } 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 413f37e1f..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 13024cb0c..993833f46 100644 --- a/LibGit2Sharp/TagFetchMode.cs +++ b/LibGit2Sharp/TagFetchMode.cs @@ -1,25 +1,31 @@ namespace LibGit2Sharp { /// - /// Describe the expected tag retrieval behavior - /// when a fetch operation is being performed. + /// Describe the expected tag retrieval behavior + /// when a fetch operation is being performed. /// public enum TagFetchMode { /// - /// No tag will be retrieved. + /// Use the setting from the configuration + /// or, when there isn't any, fallback to default behavior. /// - None = 1, // GIT_REMOTE_DOWNLOAD_TAGS_NONE + FromConfigurationOrDefault = 0, // GIT_REMOTE_DOWNLOAD_TAGS_FALLBACK /// - /// Default behavior. Will automatically retrieve tags that - /// point to objects retrieved during this fetch. + /// Will automatically retrieve tags that + /// point to objects retrieved during this fetch. /// Auto, // GIT_REMOTE_DOWNLOAD_TAGS_AUTO /// - /// All tags will be downloaded, but _only_ tags, along with - /// all the objects these tags are pointing to. + /// No tag will be retrieved. + /// + None, // GIT_REMOTE_DOWNLOAD_TAGS_NONE + + /// + /// All tags will be downloaded, but _only_ tags, along with + /// all the objects these tags are pointing to. /// All, // GIT_REMOTE_DOWNLOAD_TAGS_ALL } diff --git a/LibGit2Sharp/TarArchiver.cs b/LibGit2Sharp/TarArchiver.cs new file mode 100644 index 000000000..3c9ecdd51 --- /dev/null +++ b/LibGit2Sharp/TarArchiver.cs @@ -0,0 +1,143 @@ +using System; +using System.Globalization; +using System.IO; +using System.Text; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Logic for tar archiving (not the actual tar format, but the overal logic related to tar+git) is taken + /// from https://github.com/git/git/blob/master/archive-tar.c. + /// + internal class TarArchiver : ArchiverBase, IDisposable + { + private readonly TarWriter writer; + + public TarArchiver(FileStream output) + { + writer = new TarWriter(output); + } + + #region Overrides of ArchiverBase + + public override void BeforeArchiving(Tree tree, ObjectId oid, DateTimeOffset modificationTime) + { + if (oid == null) + { + return; + } + + // 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)))) + { + writer.Write("pax_global_header", + stream, modificationTime, + "666".OctalToInt32(), + "0", + "0", + 'g', + "root", + "root", + "0", + "0", + oid.Sha, + false); + } + } + + protected override void AddTreeEntry(string path, TreeEntry entry, DateTimeOffset modificationTime) + { + switch (entry.Mode) + { + case Mode.GitLink: + case Mode.Directory: + 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); + + 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); + } + break; + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Unsupported file mode: {0} (sha1: {1}).", + entry.Mode, + entry.TargetId.Sha)); + } + } + + private void WriteStream(string path, TreeEntry entry, DateTimeOffset modificationTime, Func streamer) + { + 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); + } + } + + #endregion + + #region Implementation of IDisposable + + public void Dispose() + { + writer.Dispose(); + } + + #endregion + } +} diff --git a/LibGit2Sharp/TransferCallbacks.cs b/LibGit2Sharp/TransferCallbacks.cs deleted file mode 100644 index d094dd0d2..000000000 --- a/LibGit2Sharp/TransferCallbacks.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using LibGit2Sharp.Core; -using LibGit2Sharp.Handlers; - -namespace LibGit2Sharp -{ - /// - /// Class to handle the mapping between libgit2 git_transfer_progress_callback function and - /// a corresponding . Generates a delegate that - /// wraps the delegate with a delegate that matches - /// the git_transfer_progress_callback signature. - /// - internal class TransferCallbacks - { - /// - /// Managed delegate to be called in response to a git_transfer_progress_callback callback from libgit2. - /// - private readonly TransferProgressHandler onTransferProgress; - - /// - /// Constructor to set up the native callback given managed delegate. - /// - /// The delegate that the git_transfer_progress_callback will call. - private TransferCallbacks(TransferProgressHandler onTransferProgress) - { - this.onTransferProgress = onTransferProgress; - } - - /// - /// Generates a delegate that matches the native git_transfer_progress_callback function's signature and wraps the delegate. - /// - /// The delegate to call in responde to a the native git_transfer_progress_callback callback. - /// A delegate method with a signature that matches git_transfer_progress_callback. - internal static NativeMethods.git_transfer_progress_callback GenerateCallback(TransferProgressHandler onTransferProgress) - { - if (onTransferProgress == null) - { - return null; - } - - return new TransferCallbacks(onTransferProgress).OnGitTransferProgress; - } - - /// - /// The delegate with the signature that matches the native git_transfer_progress_callback function's signature. - /// - /// structure containing progress information. - /// Payload data. - private void OnGitTransferProgress(ref GitTransferProgress progress, IntPtr payload) - { - onTransferProgress(new TransferProgress(progress)); - } - } -} diff --git a/LibGit2Sharp/TransferProgress.cs b/LibGit2Sharp/TransferProgress.cs index 25603e97c..984c1741e 100644 --- a/LibGit2Sharp/TransferProgress.cs +++ b/LibGit2Sharp/TransferProgress.cs @@ -1,22 +1,25 @@ -using LibGit2Sharp.Core; +using System.Diagnostics; +using System.Globalization; +using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Expose progress values from a fetch operation. + /// Expose progress values from a fetch operation. /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TransferProgress { private GitTransferProgress gitTransferProgress; /// - /// Empty constructor. + /// Empty constructor. /// protected TransferProgress() { } /// - /// Constructor. + /// Constructor. /// internal TransferProgress(GitTransferProgress gitTransferProgress) { @@ -24,46 +27,46 @@ internal TransferProgress(GitTransferProgress gitTransferProgress) } /// - /// Total number of objects. + /// Total number of objects. /// public virtual int TotalObjects { - get - { - return (int) gitTransferProgress.total_objects; - } + get { return (int)gitTransferProgress.total_objects; } } /// - /// Number of objects indexed. + /// Number of objects indexed. /// public virtual int IndexedObjects { - get - { - return (int) gitTransferProgress.indexed_objects; - } + get { return (int)gitTransferProgress.indexed_objects; } } /// - /// Number of objects received. + /// Number of objects received. /// public virtual int ReceivedObjects { - get - { - return (int) gitTransferProgress.received_objects; - } + get { return (int)gitTransferProgress.received_objects; } } /// - /// Number of bytes received. + /// 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 44b764b7c..30f534a99 100644 --- a/LibGit2Sharp/Tree.cs +++ b/LibGit2Sharp/Tree.cs @@ -5,124 +5,123 @@ 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. + /// 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; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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 . + /// 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. + /// 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. + /// 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)); } } - /// - /// Gets the s immediately under this . - /// - public virtual IEnumerable Trees + internal string Path => path; + + #region IEnumerable Members + + unsafe TreeEntry byIndex(ObjectSafeWrapper obj, uint i, ObjectId parentTreeId, Repository repo, string parentPath) { - get + using (var entryHandle = Proxy.git_tree_entry_byindex(obj.ObjectPtr, i)) { - return this - .Where(e => e.Type == GitObjectType.Tree) - .Select(e => e.Target) - .Cast(); + return new TreeEntry(entryHandle, parentTreeId, repo, parentPath); } } - /// - /// Gets the s immediately under this . - /// - public virtual IEnumerable Blobs + internal static string CombinePath(string a, string b) { - get + var bld = new StringBuilder(); + bld.Append(a); + if (!string.IsNullOrEmpty(a) && + !a.EndsWith("/", StringComparison.Ordinal) && + !b.StartsWith("/", StringComparison.Ordinal)) { - return this - .Where(e => e.Type == GitObjectType.Blob) - .Select(e => e.Target) - .Cast(); + bld.Append('/'); } - } + bld.Append(b); - internal string Path - { - get { return path.Native; } + return bld.ToString(); } - #region IEnumerable Members - /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate 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); } } } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. + /// Throws if tree is missing IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -135,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 359134f7e..6a54d9c09 100644 --- a/LibGit2Sharp/TreeChanges.cs +++ b/LibGit2Sharp/TreeChanges.cs @@ -4,127 +4,101 @@ using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Text; using LibGit2Sharp.Core; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// Holds the result of a diff between two trees. - /// Changes at the granularity of the file can be obtained through the different sub-collections , and . + /// Holds the result of a diff between two trees. + /// Changes at the granularity of the file can be obtained through the different sub-collections , and . + /// 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 int linesAdded; - private int linesDeleted; - - private readonly IDictionary> fileDispatcher = Build(); - - private readonly StringBuilder fullPatchBuilder = new StringBuilder(); - private static readonly Comparison ordinalComparer = - (one, other) => string.CompareOrdinal(one.Path, other.Path); - - 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) }, - }; - } + private readonly DiffHandle diff; + private readonly Lazy count; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected TreeChanges() { } - internal TreeChanges(DiffListSafeHandle diff) - { - Proxy.git_diff_print_patch(diff, PrintCallBack); - added.Sort(ordinalComparer); - deleted.Sort(ordinalComparer); - modified.Sort(ordinalComparer); - } - - private int PrintCallBack(GitDiffDelta delta, GitDiffRange range, GitDiffLineOrigin lineorigin, IntPtr content, UIntPtr contentlen, IntPtr payload) + internal unsafe TreeChanges(DiffHandle diff) { - string formattedoutput = Utf8Marshaler.FromNative(content, (int)contentlen); - - TreeEntryChanges currentChange = AddFileChange(delta, lineorigin); - AddLineChange(currentChange, lineorigin); - - currentChange.AppendToPatch(formattedoutput); - fullPatchBuilder.Append(formattedoutput); - - return 0; + this.diff = diff; + this.count = new Lazy(() => Proxy.git_diff_num_deltas(diff)); } - private void AddLineChange(Changes currentChange, GitDiffLineOrigin lineOrigin) + /// + /// Enumerates the diff and yields deltas with the specified change kind. + /// + /// Change type to filter on. + private IEnumerable GetChangesOfKind(ChangeKind changeKind) { - switch (lineOrigin) + TreeEntryChanges entry; + for (int i = 0; i < Count; i++) { - case GitDiffLineOrigin.GIT_DIFF_LINE_ADDITION: - linesAdded++; - currentChange.LinesAdded++; - break; - - case GitDiffLineOrigin.GIT_DIFF_LINE_DELETION: - linesDeleted++; - currentChange.LinesDeleted++; - break; + if (TryGetEntryWithChangeTypeAt(i, changeKind, out entry)) + { + yield return entry; + } } } - private TreeEntryChanges AddFileChange(GitDiffDelta delta, GitDiffLineOrigin lineorigin) + /// + /// 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 newFilePath = FilePathMarshaler.FromNative(delta.NewFile.Path); - - if (lineorigin != GitDiffLineOrigin.GIT_DIFF_LINE_FILE_HDR) - return this[newFilePath]; + 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."); - var oldFilePath = FilePathMarshaler.FromNative(delta.OldFile.Path); - var newMode = (Mode)delta.NewFile.Mode; - var oldMode = (Mode)delta.OldFile.Mode; - var newOid = delta.NewFile.Oid; - var oldOid = delta.OldFile.Oid; + var delta = Proxy.git_diff_get_delta(diff, index); - if (delta.Status == ChangeKind.Untracked) + if (TreeEntryChanges.GetStatusFromChangeKind(delta->status) == changeKind) { - delta.Status = ChangeKind.Added; + entry = new TreeEntryChanges(delta); + return true; } - var diffFile = new TreeEntryChanges(newFilePath, newMode, newOid, delta.Status, oldFilePath, oldMode, oldOid, delta.IsBinary()); - - fileDispatcher[delta.Status](this, diffFile); - changes.Add(newFilePath, diffFile); - return diffFile; + entry = null; + return false; } - #region IEnumerable Members + #region IEnumerable Members /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// 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); + } + } + + /// + /// This is method exists to work around .net not allowing unsafe code + /// in iterators. + /// + private unsafe TreeEntryChanges GetEntryAt(int index) + { + 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."); + + return new TreeEntryChanges(Proxy.git_diff_get_delta(diff, index)); } /// - /// Returns an enumerator that iterates through the collection. + /// Returns an enumerator that iterates through the collection. /// - /// An object that can be used to iterate through the collection. + /// An object that can be used to iterate through the collection. IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); @@ -133,81 +107,75 @@ IEnumerator IEnumerable.GetEnumerator() #endregion /// - /// Gets the corresponding to the specified . + /// List of that have been been added. /// - public virtual TreeEntryChanges this[string path] + public virtual IEnumerable Added { - get { return this[(FilePath)path]; } + get { return GetChangesOfKind(ChangeKind.Added); } } - private TreeEntryChanges this[FilePath path] + /// + /// List of that have been deleted. + /// + public virtual IEnumerable Deleted { - get - { - TreeEntryChanges treeEntryChanges; - if (changes.TryGetValue(path, out treeEntryChanges)) - { - return treeEntryChanges; - } - - return null; - } + get { return GetChangesOfKind(ChangeKind.Deleted); } } /// - /// List of that have been been added. + /// List of that have been modified. /// - public virtual IEnumerable Added + public virtual IEnumerable Modified { - get { return added; } + get { return GetChangesOfKind(ChangeKind.Modified); } } /// - /// List of that have been deleted. + /// List of which type have been changed. /// - public virtual IEnumerable Deleted + public virtual IEnumerable TypeChanged { - get { return deleted; } + get { return GetChangesOfKind(ChangeKind.TypeChanged); } } /// - /// List of that have been modified. + /// List of which have been renamed /// - public virtual IEnumerable Modified + public virtual IEnumerable Renamed { - get { return modified; } + get { return GetChangesOfKind(ChangeKind.Renamed); } } /// - /// List of which type have been changed. + /// List of which have been copied /// - public virtual IEnumerable TypeChanged + public virtual IEnumerable Copied { - get { return typeChanged; } + get { return GetChangesOfKind(ChangeKind.Copied); } } /// - /// The total number of lines added in this diff. + /// List of which are unmodified /// - public virtual int LinesAdded + public virtual IEnumerable Unmodified { - get { return linesAdded; } + get { return GetChangesOfKind(ChangeKind.Unmodified); } } /// - /// The total number of lines added in this diff. + /// List of which are conflicted /// - public virtual int LinesDeleted + public virtual IEnumerable Conflicted { - get { return linesDeleted; } + get { return GetChangesOfKind(ChangeKind.Conflicted); } } /// - /// The full patch file of this diff. + /// Gets the number of in this comparison. /// - public virtual string Patch + public virtual int Count { - get { return fullPatchBuilder.ToString(); } + get { return count.Value; } } private string DebuggerDisplay @@ -215,10 +183,32 @@ private string DebuggerDisplay get { return string.Format(CultureInfo.InvariantCulture, - "+{0} ~{2} -{1} \u00B1{3}", - Added.Count(), Deleted.Count(), - Modified.Count(), TypeChanged.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 f312a9a2e..91389f6e3 100644 --- a/LibGit2Sharp/TreeDefinition.cs +++ b/LibGit2Sharp/TreeDefinition.cs @@ -3,13 +3,12 @@ using System.Globalization; using System.IO; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// Holds the meta data of a . + /// Holds the meta data of a . /// public class TreeDefinition { @@ -17,10 +16,10 @@ public class TreeDefinition private readonly Dictionary unwrappedTrees = new Dictionary(); /// - /// Builds a from an existing . + /// Builds a from an existing . /// - /// The to be processed. - /// A new holding the meta data of the . + /// The to be processed. + /// A new holding the meta data of the . public static TreeDefinition From(Tree tree) { Ensure.ArgumentNotNull(tree, "tree"); @@ -29,12 +28,24 @@ public static TreeDefinition From(Tree tree) foreach (TreeEntry treeEntry in tree) { - td.AddEntry(treeEntry.Name, TreeEntryDefinition.From(treeEntry)); + td.Add(treeEntry.Name, treeEntry); } return td; } + /// + /// Builds a from a 's . + /// + /// The whose tree is to be processed + /// A new holding the meta data of the 's . + public static TreeDefinition From(Commit commit) + { + Ensure.ArgumentNotNull(commit, "commit"); + + return From(commit.Tree); + } + private void AddEntry(string targetTreeEntryName, TreeEntryDefinition treeEntryDefinition) { if (entries.ContainsKey(targetTreeEntryName)) @@ -47,7 +58,25 @@ private void AddEntry(string targetTreeEntryName, TreeEntryDefinition treeEntryD } /// - /// Removes a located the specified path. + /// 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. /// /// The path within this . /// The current . @@ -65,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)) @@ -87,7 +117,7 @@ public virtual TreeDefinition Remove(string treeEntryPath) } /// - /// Adds or replaces a at the specified location. + /// Adds or replaces a at the specified location. /// /// The path within this . /// The to be stored at the described location. @@ -97,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); @@ -126,7 +154,22 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntryDefinitio } /// - /// Adds or replaces a , dynamically built from the provided , at the specified location. + /// Adds or replaces a , built from the provided , at the specified location. + /// + /// The path within this . + /// The to be stored at the described location. + /// The current . + public virtual TreeDefinition Add(string targetTreeEntryPath, TreeEntry treeEntry) + { + Ensure.ArgumentNotNull(treeEntry, "treeEntry"); + + TreeEntryDefinition ted = TreeEntryDefinition.From(treeEntry); + + return Add(targetTreeEntryPath, ted); + } + + /// + /// Adds or replaces a , dynamically built from the provided , at the specified location. /// /// The path within this . /// The to be stored at the described location. @@ -143,11 +186,11 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, Blob blob, Mode mo } /// - /// Adds or replaces a , dynamically built from the content of the file, at the specified location. + /// Adds or replaces a , dynamically built from the content of the file, at the specified location. /// /// The path within this . /// The path to the file from which a will be built and stored at the described location. A relative path is allowed to be passed if the target - /// is a standard, non-bare, repository. The path will then be considered as a path relative to the root of the working directory. + /// is a standard, non-bare, repository. The path will then be considered as a path relative to the root of the working directory. /// The file related attributes. /// The current . public virtual TreeDefinition Add(string targetTreeEntryPath, string filePath, Mode mode) @@ -160,7 +203,24 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, string filePath, M } /// - /// Adds or replaces a , dynamically built from the provided , at the specified location. + /// 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. /// /// The path within this . /// The to be stored at the described location. @@ -174,6 +234,35 @@ public virtual TreeDefinition Add(string targetTreeEntryPath, Tree tree) return Add(targetTreeEntryPath, ted); } + /// + /// Adds or replaces a gitlink equivalent to . + /// + /// The to be linked. + /// The current . + public virtual TreeDefinition Add(Submodule submodule) + { + Ensure.ArgumentNotNull(submodule, "submodule"); + + return AddGitLink(submodule.Path, submodule.HeadCommitId); + } + + /// + /// Adds or replaces a gitlink , + /// referencing the commit identified by , + /// at the specified location. + /// + /// The path within this . + /// The of the commit to be linked at the described location. + /// The current . + public virtual TreeDefinition AddGitLink(string targetTreeEntryPath, ObjectId objectId) + { + Ensure.ArgumentNotNull(objectId, "objectId"); + + var ted = TreeEntryDefinition.From(objectId); + + return Add(targetTreeEntryPath, ted); + } + private TreeDefinition RetrieveOrBuildTreeDefinition(string treeName, bool shouldOverWrite) { TreeDefinition td; @@ -188,13 +277,14 @@ private TreeDefinition RetrieveOrBuildTreeDefinition(string treeName, bool shoul if (hasAnEntryBeenFound) { - switch (treeEntryDefinition.Type) + switch (treeEntryDefinition.TargetType) { - case GitObjectType.Tree: + case TreeEntryTargetType.Tree: td = From(treeEntryDefinition.Target as Tree); break; - case GitObjectType.Blob: + case TreeEntryTargetType.Blob: + case TreeEntryTargetType.GitLink: if (shouldOverWrite) { td = new TreeDefinition(); @@ -227,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); @@ -253,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; } } @@ -276,7 +371,7 @@ private void WrapTree(string entryName, TreeEntryDefinition treeEntryDefinition) } /// - /// Retrieves the located the specified path. + /// Retrieves the located the specified path. /// /// The path within this . /// The found if any; null otherwise. @@ -291,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; @@ -299,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)))) { @@ -313,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) @@ -325,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/TreeEntry.cs b/LibGit2Sharp/TreeEntry.cs index 57dc6b232..943e14570 100644 --- a/LibGit2Sharp/TreeEntry.cs +++ b/LibGit2Sharp/TreeEntry.cs @@ -1,14 +1,15 @@ using System; +using System.Diagnostics; using System.Globalization; -using System.Runtime.InteropServices; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; +using LibGit2Sharp.Core.Handles; namespace LibGit2Sharp { /// - /// Representation of an entry in a . + /// Representation of an entry in a . /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class TreeEntry : IEquatable { private readonly ObjectId parentTreeId; @@ -21,42 +22,45 @@ public class TreeEntry : IEquatable new LambdaEqualityHelper(x => x.Name, x => x.parentTreeId); /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// 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); - Type = Proxy.git_tree_entry_type(obj); + targetOid = Proxy.git_tree_entry_id(entry); + + 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)); } /// - /// Gets the file mode. + /// Gets the file mode. /// public virtual Mode Mode { get; private set; } /// - /// Gets the filename. + /// Gets the filename. /// public virtual string Name { get; private set; } /// - /// Gets the path. - /// The path is expressed in a relative form from the latest known . Path segments are separated with a forward or backslash, depending on the OS the libray is being run on."/> + /// Gets the path. + /// The path is expressed in a relative form from the latest known . Path segments are separated with a forward or backslash, depending on the OS the libray is being run on."/> /// public virtual string Path { get { return path.Value; } } /// - /// Gets the being pointed at. + /// Gets the being pointed at. /// public virtual GitObject Target { get { return target.Value; } } @@ -66,42 +70,50 @@ internal ObjectId TargetId } /// - /// Gets the of the being pointed at. + /// Gets the of the being pointed at. /// - public virtual GitObjectType Type { get; private set; } + public virtual TreeEntryTargetType TargetType { get; private set; } private GitObject RetrieveTreeEntryTarget() { - if (!Type.HasAny(new[]{GitObjectType.Tree, GitObjectType.Blob})) + switch (TargetType) { - throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "TreeEntry target of type '{0}' are not supported.", Type)); - } + case TreeEntryTargetType.GitLink: + return new GitLink(repo, targetOid); - return GitObject.BuildFrom(repo, targetOid, Type, Path); + case TreeEntryTargetType.Blob: + case TreeEntryTargetType.Tree: + 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)); + } } /// - /// 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); } /// - /// 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 bool Equals(TreeEntry other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -110,10 +122,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(TreeEntry left, TreeEntry right) { @@ -121,14 +133,25 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(TreeEntry left, TreeEntry right) { 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 24bc2c634..1ab1a6172 100644 --- a/LibGit2Sharp/TreeEntryChanges.cs +++ b/LibGit2Sharp/TreeEntryChanges.cs @@ -5,71 +5,114 @@ namespace LibGit2Sharp { /// - /// Holds the changes between two versions of a tree entry. + /// Holds the changes between two versions of a tree entry. /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class TreeEntryChanges : Changes + public class TreeEntryChanges { /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected TreeEntryChanges() { } - internal TreeEntryChanges(FilePath path, Mode mode, ObjectId oid, ChangeKind status, FilePath oldPath, Mode oldMode, ObjectId oldOid, bool isBinaryComparison) + internal unsafe TreeEntryChanges(git_diff_delta* delta) { - Path = path.Native; - Mode = mode; - Oid = oid; - Status = status; - OldPath = oldPath.Native; - OldMode = oldMode; - OldOid = oldOid; - IsBinaryComparison = isBinaryComparison; + Path = LaxUtf8Marshaler.FromNative(delta->new_file.Path); + OldPath = LaxUtf8Marshaler.FromNative(delta->old_file.Path); + + 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 = 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; + } } /// - /// The new path. + /// The new path. /// public virtual string Path { get; private set; } /// - /// The new . + /// The new . /// public virtual Mode Mode { get; private set; } /// - /// The new content hash. + /// The new content hash. /// public virtual ObjectId Oid { get; private set; } /// - /// The kind of change that has been done (added, deleted, modified ...). + /// 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 ...). /// public virtual ChangeKind Status { get; private set; } /// - /// The old path. + /// The old path. /// public virtual string OldPath { get; private set; } /// - /// The old . + /// The old . /// public virtual Mode OldMode { get; private set; } /// - /// The old content hash. + /// The old content hash. /// 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 d46ba7b29..d32cc722c 100644 --- a/LibGit2Sharp/TreeEntryDefinition.cs +++ b/LibGit2Sharp/TreeEntryDefinition.cs @@ -1,40 +1,38 @@ using System; using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; namespace LibGit2Sharp { /// - /// Holds the meta data of a . + /// Holds the meta data of a . /// public class TreeEntryDefinition : IEquatable { private Lazy target; private static readonly LambdaEqualityHelper equalityHelper = - new LambdaEqualityHelper(x => x.Mode, x => x.Type, x => x.TargetId); + new LambdaEqualityHelper(x => x.Mode, x => x.TargetType, x => x.TargetId); - internal static readonly Mode[] BlobModes = new[] { Mode.NonExecutableFile, Mode.ExecutableFile, Mode.NonExecutableGroupWritableFile, Mode.SymbolicLink }; + internal static readonly Enum[] BlobModes = new Enum[] { Mode.NonExecutableFile, Mode.ExecutableFile, Mode.NonExecutableGroupWritableFile, Mode.SymbolicLink }; /// - /// Needed for mocking purposes. + /// Needed for mocking purposes. /// protected TreeEntryDefinition() - { - } + { } /// - /// Gets file mode. + /// Gets file mode. /// public virtual Mode Mode { get; private set; } /// - /// Gets the of the target being pointed at. + /// Gets the of the target being pointed at. /// - public virtual GitObjectType Type { get; private set; } + public virtual TreeEntryTargetType TargetType { get; private set; } /// - /// Gets the of the target being pointed at. + /// Gets the of the target being pointed at. /// public virtual ObjectId TargetId { get; private set; } @@ -46,23 +44,38 @@ internal virtual GitObject Target internal static TreeEntryDefinition From(TreeEntry treeEntry) { return new TreeEntryDefinition - { - Mode = treeEntry.Mode, - Type = treeEntry.Type, - 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, - Type = GitObjectType.Blob, - TargetId = blob.Id, - target = new Lazy(() => blob) - }; + { + Mode = mode, + TargetType = TreeEntryTargetType.Blob, + TargetId = id + }; } internal static TreeEntryDefinition TransientBlobFrom(string filePath, Mode mode) @@ -70,45 +83,56 @@ 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."); }), + }; } internal static TreeEntryDefinition From(Tree tree) { return new TreeEntryDefinition - { - Mode = Mode.Directory, - Type = GitObjectType.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); } /// - /// 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 bool Equals(TreeEntryDefinition other) { return equalityHelper.Equals(this, other); } /// - /// Returns the hash code for this instance. + /// Returns the hash code for this instance. /// /// A 32-bit signed integer hash code. public override int GetHashCode() @@ -117,10 +141,10 @@ public override int GetHashCode() } /// - /// Tests if two are equal. + /// Tests if two are equal. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are equal; false otherwise. public static bool operator ==(TreeEntryDefinition left, TreeEntryDefinition right) { @@ -128,10 +152,10 @@ public override int GetHashCode() } /// - /// Tests if two are different. + /// Tests if two are different. /// - /// First to compare. - /// Second to compare. + /// First to compare. + /// Second to compare. /// True if the two objects are different; false otherwise. public static bool operator !=(TreeEntryDefinition left, TreeEntryDefinition right) { @@ -159,17 +183,17 @@ public override Mode Mode get { return Mode.Directory; } } - public override GitObjectType Type + public override TreeEntryTargetType TargetType { - get { return GitObjectType.Tree; } + get { return TreeEntryTargetType.Tree; } } } internal class TransientBlobTreeEntryDefinition : TransientTreeEntryDefinition { - public override GitObjectType Type + public override TreeEntryTargetType TargetType { - get { return GitObjectType.Blob; } + get { return TreeEntryTargetType.Blob; } } public Func Builder { get; set; } diff --git a/LibGit2Sharp/TreeEntryTargetType.cs b/LibGit2Sharp/TreeEntryTargetType.cs new file mode 100644 index 000000000..181708ec7 --- /dev/null +++ b/LibGit2Sharp/TreeEntryTargetType.cs @@ -0,0 +1,47 @@ +using System; +using System.Globalization; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Underlying type of the target a + /// + public enum TreeEntryTargetType + { + /// + /// A file revision object. + /// + Blob, + + /// + /// A tree object. + /// + Tree, + + /// + /// A pointer to a commit object in another repository. + /// + GitLink, + } + + internal static class TreeEntryTargetTypeExtensions + { + public static GitObjectType ToGitObjectType(this TreeEntryTargetType type) + { + switch (type) + { + case TreeEntryTargetType.Tree: + return GitObjectType.Tree; + + case TreeEntryTargetType.Blob: + return GitObjectType.Blob; + + default: + throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, + "Cannot map {0} to a GitObjectType.", + type)); + } + } + } +} diff --git a/LibGit2Sharp/UnbornBranchException.cs b/LibGit2Sharp/UnbornBranchException.cs new file mode 100644 index 000000000..8f01a63ab --- /dev/null +++ b/LibGit2Sharp/UnbornBranchException.cs @@ -0,0 +1,60 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif + +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. + /// + /// 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. + /// + /// 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 UnbornBranchException(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 UnbornBranchException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/UnmatchedPathException.cs b/LibGit2Sharp/UnmatchedPathException.cs new file mode 100644 index 000000000..96e5654c7 --- /dev/null +++ b/LibGit2Sharp/UnmatchedPathException.cs @@ -0,0 +1,59 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#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. + /// + /// 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. + /// + /// 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 UnmatchedPathException(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 UnmatchedPathException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + } +} diff --git a/LibGit2Sharp/UnmergedIndexEntriesException.cs b/LibGit2Sharp/UnmergedIndexEntriesException.cs index 5cd7ff217..f9f1a834b 100644 --- a/LibGit2Sharp/UnmergedIndexEntriesException.cs +++ b/LibGit2Sharp/UnmergedIndexEntriesException.cs @@ -1,55 +1,73 @@ using System; +#if NETFRAMEWORK using System.Runtime.Serialization; +#endif using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// The exception that is thrown when an operation that requires a fully merged index - /// is performed against an index with unmerged entries + /// 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. + /// Initializes a new instance of the class. /// public UnmergedIndexEntriesException() - { - } + { } /// - /// Initializes a new instance of the class with a specified error message. + /// Initializes a new instance of the class with a specified error message. /// - /// A message that describes the error. + /// 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. + /// 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 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 UnmergedIndexEntriesException(string message, Exception innerException) : base(message, innerException) - { - } + { } +#if NETFRAMEWORK /// - /// Initializes a new instance of the class with a serialized data. + /// 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. + /// The that holds the serialized object data about the exception being thrown. + /// 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 new file mode 100644 index 000000000..f3c6af7dd --- /dev/null +++ b/LibGit2Sharp/UserCanceledException.cs @@ -0,0 +1,72 @@ +using System; +#if NETFRAMEWORK +using System.Runtime.Serialization; +#endif +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// The exception that is thrown when an operation is canceled. + /// +#if NETFRAMEWORK + [Serializable] +#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. + /// + /// 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. + /// + /// 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 UserCancelledException(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 UserCancelledException(SerializationInfo info, StreamingContext context) + : base(info, context) + { } +#endif + + internal UserCancelledException(string message, GitErrorCategory category) + : base(message, category) + { } + + internal override GitErrorCode ErrorCode + { + get + { + return GitErrorCode.User; + } + } + } +} diff --git a/LibGit2Sharp/UsernamePasswordCredentials.cs b/LibGit2Sharp/UsernamePasswordCredentials.cs new file mode 100644 index 000000000..761be5c74 --- /dev/null +++ b/LibGit2Sharp/UsernamePasswordCredentials.cs @@ -0,0 +1,47 @@ +using System; +using System.Text; +using System.Runtime.InteropServices; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Class that holds username and password credentials for remote repository access. + /// + public sealed class UsernamePasswordCredentials : 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."); + } + + 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). + /// + public string Username { get; set; } + + /// + /// Password for username/password authentication (as in HTTP basic auth). + /// + public string Password { get; set; } + } +} diff --git a/LibGit2Sharp/Version.cs b/LibGit2Sharp/Version.cs new file mode 100644 index 000000000..2c21ccad2 --- /dev/null +++ b/LibGit2Sharp/Version.cs @@ -0,0 +1,85 @@ +using System.Globalization; +using System.Reflection; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + /// + /// Gets the current LibGit2Sharp version. + /// + public class Version + { + /// + /// Needed for mocking purposes. + /// + protected Version() + { } + + internal static Version Build() + { + return new Version(); + } + + /// + /// Returns version of the LibGit2Sharp library. + /// + public virtual string InformationalVersion + { + get + { + var attribute = Assembly.GetExecutingAssembly().GetCustomAttribute(); + return attribute.InformationalVersion; + } + } + + /// + /// Returns all the optional features that were compiled into + /// libgit2. + /// + /// A enumeration. + public virtual BuiltInFeatures Features + { + get { return Proxy.git_libgit2_features(); } + } + + /// + /// Returns the SHA hash for the libgit2 library. + /// + public virtual string LibGit2CommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2CommitSha); + + /// + /// Returns the SHA hash for the LibGit2Sharp library. + /// + public virtual string LibGit2SharpCommitSha => RetrieveAbbrevShaFrom(AssemblyCommitIds.LibGit2SharpCommitSha); + + private string RetrieveAbbrevShaFrom(string sha) + { + var index = sha.Length > 7 ? 7 : sha.Length; + return sha.Substring(0, index); + } + + /// + /// Returns a string representing the LibGit2Sharp version. + /// + /// + /// The format of the version number is as follows: + /// Major.Minor.Patch[-previewTag]+libgit2-{libgit2_abbrev_hash}.{LibGit2Sharp_hash} (arch - features) + /// + /// + public override string ToString() + { + return RetrieveVersion(); + } + + private string RetrieveVersion() + { + string features = Features.ToString(); + + return string.Format(CultureInfo.InvariantCulture, + "{0} ({1} - {2})", + InformationalVersion, + Platform.ProcessorArchitecture, + features); + } + } +} diff --git a/LibGit2Sharp/VoidReference.cs b/LibGit2Sharp/VoidReference.cs index b8defc8c0..cc20d1e39 100644 --- a/LibGit2Sharp/VoidReference.cs +++ b/LibGit2Sharp/VoidReference.cs @@ -2,8 +2,8 @@ { internal class VoidReference : Reference { - internal VoidReference(string canonicalName) - : base(canonicalName, null) + internal VoidReference(IRepository repo, string canonicalName) + : base(repo, canonicalName, null) { } public override DirectReference ResolveToDirectReference() 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 9e5aa9d2e..000000000 --- a/LibGit2Sharp/libgit2_hash.txt +++ /dev/null @@ -1 +0,0 @@ -40a605104c2060c2bc5a08dfb62cafe473baeb21 diff --git a/LibGit2Sharp/libgit2sharp_hash.txt b/LibGit2Sharp/libgit2sharp_hash.txt deleted file mode 100644 index 0faec6021..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/ResharperSettings.xml b/ResharperSettings.xml deleted file mode 100644 index 3afd3d805..000000000 --- a/ResharperSettings.xml +++ /dev/null @@ -1,319 +0,0 @@ - - - - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - ALWAYS_ADD - 1 - 1 - True - - public - protected - internal - private - new - abstract - virtual - override - sealed - static - readonly - extern - unsafe - volatile - - False - False - False - True - False - - - - $object$_On$event$ - $event$Handler - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -]]> - - - - - - $object$_On$event$ - $event$Handler - - - - - - - - - - - - - - - - - - - - - - - $object$_On$event$ - $event$Handler - - - - - - - - - - - - - - - - - - \ No newline at end of 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 c768b14d6..000000000 --- a/UpdateLibgit2ToSha.ps1 +++ /dev/null @@ -1,144 +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) and "11". -.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', - [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 - -& { - 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 - } - - 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 "BUILD_CLAR=$build_clar" .. } - Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } - if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } - cd $configuration - Run-Command -Quiet { & rm *.exp } - 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 "BUILD_CLAR=$build_clar" ../.. } - Run-Command -Quiet -Fatal { & $cmake --build . --config $configuration } - if ($test.IsPresent) { Run-Command -Quiet -Fatal { & $ctest -V . } } - cd $configuration - Run-Command -Quiet { & rm *.exp } - Run-Command -Quiet -Fatal { & copy -fo * $x64Directory } - - Write-Output "Done!" - pop-location - sc -Encoding UTF8 libgit2sharp\libgit2_hash.txt $sha -} -exit diff --git a/acknowledgments.md b/acknowledgments.md new file mode 100644 index 000000000..a04ce2847 --- /dev/null +++ b/acknowledgments.md @@ -0,0 +1,2 @@ +LibGit2Sharp is making use of the following OSS projects: +- [tar-cs](http://code.google.com/p/tar-cs/) project by Vladimir Vasiltsov and is used under the [BSD license](http://code.google.com/p/tar-cs/source/browse/trunk/COPYING) diff --git a/build.libgit2sharp.cmd b/build.libgit2sharp.cmd deleted file mode 100644 index f1d19c36b..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 100644 index 5d67d5649..000000000 --- a/build.libgit2sharp.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -PREVIOUS_LD=$LD_LIBRARY_PATH - -mkdir cmake-build && cd cmake-build - -cmake -DBUILD_SHARED_LIBS:BOOL=ON -DTHREADSAFE:BOOL=ON -DBUILD_CLAR:BOOL=OFF -DCMAKE_INSTALL_PREFIX=./libgit2-bin ../libgit2 -cmake --build . --target install - -LD_LIBRARY_PATH=$PWD/libgit2-bin/lib:$LD_LIBRARY_PATH -export LD_LIBRARY_PATH - -cd .. - -echo $LD_LIBRARY_PATH -xbuild CI-build.msbuild /t:Deploy - -LD_LIBRARY_PATH=$PREVIOUS_LD -export LD_LIBRARY_PATH diff --git a/build.libgit2sharp.x64.cmd b/build.libgit2sharp.x64.cmd deleted file mode 100644 index 8a4909aef..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 40a605104..000000000 --- a/libgit2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 40a605104c2060c2bc5a08dfb62cafe473baeb21 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 fdbb39152..000000000 --- a/nuget.package/LibGit2Sharp.nuspec +++ /dev/null @@ -1,24 +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/raw/master/square-logo.png - libgit2 git wrapper bindings API dvcs vcs - - - - - - - - - - - diff --git a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 b/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 deleted file mode 100644 index 6d4a41fa2..000000000 --- a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -$solutionDir = [System.IO.Path]::GetDirectoryName($dte.Solution.FullName) + "\" -$path = $installPath.Replace($solutionDir, "`$(SolutionDir)") - -$NativeAssembliesDir = Join-Path $path "NativeBinaries" -$x86 = $(Join-Path $NativeAssembliesDir "x86\*.*") -$x64 = $(Join-Path $NativeAssembliesDir "amd64\*.*") - -$LibGit2SharpPostBuildCmd = " -if not exist `"`$(TargetDir)NativeBinaries`" md `"`$(TargetDir)NativeBinaries`" -if not exist `"`$(TargetDir)NativeBinaries\x86`" md `"`$(TargetDir)NativeBinaries\x86`" -xcopy /s /y `"$x86`" `"`$(TargetDir)NativeBinaries\x86`" -if not exist `"`$(TargetDir)NativeBinaries\amd64`" md `"`$(TargetDir)NativeBinaries\amd64`" -xcopy /s /y `"$x64`" `"`$(TargetDir)NativeBinaries\amd64`"" \ No newline at end of file diff --git a/nuget.package/Tools/install.ps1 b/nuget.package/Tools/install.ps1 deleted file mode 100644 index bc403c27b..000000000 --- a/nuget.package/Tools/install.ps1 +++ /dev/null @@ -1,11 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -. (Join-Path $toolsPath "GetLibGit2SharpPostBuildCmd.ps1") - -# Get the current Post Build Event cmd -$currentPostBuildCmd = $project.Properties.Item("PostBuildEvent").Value - -# Append our post build command if it's not already there -if (!$currentPostBuildCmd.Contains($LibGit2SharpPostBuildCmd)) { - $project.Properties.Item("PostBuildEvent").Value += $LibGit2SharpPostBuildCmd -} \ No newline at end of file diff --git a/nuget.package/Tools/uninstall.ps1 b/nuget.package/Tools/uninstall.ps1 deleted file mode 100644 index a1854cb32..000000000 --- a/nuget.package/Tools/uninstall.ps1 +++ /dev/null @@ -1,9 +0,0 @@ -param($installPath, $toolsPath, $package, $project) - -. (Join-Path $toolsPath "GetLibGit2SharpPostBuildCmd.ps1") - -# Get the current Post Build Event cmd -$currentPostBuildCmd = $project.Properties.Item("PostBuildEvent").Value - -# Remove our post build command from it (if it's there) -$project.Properties.Item("PostBuildEvent").Value = $currentPostBuildCmd.Replace($LibGit2SharpPostBuildCmd, "") \ No newline at end of file diff --git a/nuget.package/build.nuget.package.cmd b/nuget.package/build.nuget.package.cmd deleted file mode 100644 index e36c3f156..000000000 --- a/nuget.package/build.nuget.package.cmd +++ /dev/null @@ -1,26 +0,0 @@ -SETLOCAL -SET BASEDIR=%~dp0 -SET SRCDIR=%BASEDIR%..\LibGit2Sharp\ -SET CommitSha=%~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%